diff --git a/library/protobuf/LocalStorageProtocol.proto b/library/protobuf/LocalStorageProtocol.proto new file mode 100644 index 0000000000..22378423d8 --- /dev/null +++ b/library/protobuf/LocalStorageProtocol.proto @@ -0,0 +1,54 @@ +package textsecure; + +option java_package = "org.whispersystems.textsecure.storage"; +option java_outer_classname = "StorageProtos"; + +message SessionStructure { + message Chain { + optional bytes senderEphemeral = 1; + optional bytes senderEphemeralPrivate = 2; + + message ChainKey { + optional uint32 index = 1; + optional bytes key = 2; + } + + optional ChainKey chainKey = 3; + + message MessageKey { + optional uint32 index = 1; + optional bytes cipherKey = 2; + optional bytes macKey = 3; + } + + repeated MessageKey messageKeys = 4; + } + + message PendingKeyExchange { + optional uint32 sequence = 1; + optional bytes localBaseKey = 2; + optional bytes localBaseKeyPrivate = 3; + optional bytes localEphemeralKey = 4; + optional bytes localEphemeralKeyPrivate = 5; + optional bytes localIdentityKey = 7; + optional bytes localIdentityKeyPrivate = 8; + } + + message PendingPreKey { + optional uint32 preKeyId = 1; + optional bytes baseKey = 2; + } + + optional uint32 sessionVersion = 1; + optional bytes localIdentityPublic = 2; + optional bytes remoteIdentityPublic = 3; + + optional bytes rootKey = 4; + optional uint32 previousCounter = 5; + + optional Chain senderChain = 6; + repeated Chain receiverChains = 7; + + optional PendingKeyExchange pendingKeyExchange = 8; + optional PendingPreKey pendingPreKey = 9; +} \ No newline at end of file diff --git a/library/protobuf/Makefile b/library/protobuf/Makefile index 55640f5437..2354c14d61 100644 --- a/library/protobuf/Makefile +++ b/library/protobuf/Makefile @@ -1,3 +1,3 @@ all: - protoc --java_out=../src/ IncomingPushMessageSignal.proto + protoc --java_out=../src/ IncomingPushMessageSignal.proto WhisperTextProtocol.proto LocalStorageProtocol.proto diff --git a/library/protobuf/WhisperTextProtocol.proto b/library/protobuf/WhisperTextProtocol.proto new file mode 100644 index 0000000000..f14625b96b --- /dev/null +++ b/library/protobuf/WhisperTextProtocol.proto @@ -0,0 +1,25 @@ +package textsecure; + +option java_package = "org.whispersystems.textsecure.crypto.protocol"; +option java_outer_classname = "WhisperProtos"; + +message WhisperMessage { + optional bytes ephemeralKey = 1; + optional uint32 counter = 2; + optional uint32 previousCounter = 3; + optional bytes ciphertext = 4; +} + +message PreKeyWhisperMessage { + optional uint32 preKeyId = 1; + optional bytes baseKey = 2; + optional bytes identityKey = 3; + optional bytes message = 4; // WhisperMessage +} + +message KeyExchangeMessage { + optional uint32 id = 1; + optional bytes baseKey = 2; + optional bytes ephemeralKey = 3; + optional bytes identityKey = 4; +} diff --git a/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java b/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java index 742d716d58..26898a2a94 100644 --- a/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java +++ b/library/src/org/whispersystems/textsecure/crypto/IdentityKey.java @@ -47,8 +47,7 @@ public class IdentityKey implements Parcelable, SerializableKey { } }; - public static final int SIZE = 1 + ECPublicKey.KEY_SIZE; - private static final int CURRENT_VESION = 1; + public static final int NIST_SIZE = 1 + ECPublicKey.KEY_SIZE; private ECPublicKey publicKey; @@ -73,19 +72,22 @@ public class IdentityKey implements Parcelable, SerializableKey { } private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException { - int version = bytes[offset] & 0xff; - - if (version > CURRENT_VESION) - throw new InvalidKeyException("Unsupported key version: " + version); - - this.publicKey = Curve.decodePoint(bytes, offset + 1); + if ((bytes[offset] & 0xff) == 1) { + this.publicKey = Curve.decodePoint(bytes, offset +1); + } else { + this.publicKey = Curve.decodePoint(bytes, offset); + } } public byte[] serialize() { - byte[] versionBytes = {(byte)CURRENT_VESION}; - byte[] encodedKey = publicKey.serialize(); + if (publicKey.getType() == Curve.NIST_TYPE) { + byte[] versionBytes = {0x01}; + byte[] encodedKey = publicKey.serialize(); - return Util.combine(versionBytes, encodedKey); + return Util.combine(versionBytes, encodedKey); + } else { + return publicKey.serialize(); + } } public String getFingerprint() { diff --git a/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java deleted file mode 100644 index e720d1b7a7..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecure.crypto; - -import android.content.Context; -import android.util.Log; - -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; -import org.whispersystems.textsecure.storage.LocalKeyRecord; -import org.whispersystems.textsecure.storage.RemoteKeyRecord; -import org.whispersystems.textsecure.storage.SessionRecord; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -/** - * Helper class for generating key pairs and calculating ECDH agreements. - * - * @author Moxie Marlinspike - */ - -public class KeyUtil { - - public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) { - //XXX Obviously we should probably do something more thorough here eventually. - Log.w("KeyUtil", "Aborting session, deleting keys..."); - LocalKeyRecord.delete(context, recipient); - RemoteKeyRecord.delete(context, recipient); - SessionRecord.delete(context, recipient); - } - - public static boolean isSessionFor(Context context, CanonicalRecipientAddress recipient) { - Log.w("KeyUtil", "Checking session..."); - return - (LocalKeyRecord.hasRecord(context, recipient)) && - (RemoteKeyRecord.hasRecord(context, recipient)) && - (SessionRecord.hasSession(context, recipient)); - } - - public static boolean isNonPrekeySessionFor(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) { - return isSessionFor(context, recipient) && - !(new SessionRecord(context, masterSecret, recipient).isPrekeyBundleRequired()); - } - - public static boolean isIdentityKeyFor(Context context, - MasterSecret masterSecret, - CanonicalRecipientAddress recipient) - { - return isSessionFor(context, recipient) && - new SessionRecord(context, masterSecret, recipient).getIdentityKey() != null; - } - - public static LocalKeyRecord initializeRecordFor(Context context, - MasterSecret masterSecret, - CanonicalRecipientAddress recipient, - int sessionVersion) - { - Log.w("KeyUtil", "Initializing local key pairs..."); - try { - SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); - int initialId = secureRandom.nextInt(4094) + 1; - - KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(sessionVersion), masterSecret); - KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(sessionVersion), masterSecret); - LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient); - - record.setCurrentKeyPair(currentPair); - record.setNextKeyPair(nextPair); - record.save(); - - return record; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java deleted file mode 100644 index 79755a54a4..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecure.crypto; - -import android.content.Context; - -import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; - -/** - * Parses and serializes the encrypted message format. - * - * @author Moxie Marlinspike - */ - -public class MessageCipher { - - private final Context context; - private final MasterSecret masterSecret; - private final IdentityKeyPair localIdentityKey; - - public MessageCipher(Context context, MasterSecret masterSecret, IdentityKeyPair localIdentityKey) { - this.context = context.getApplicationContext(); - this.masterSecret = masterSecret; - this.localIdentityKey = localIdentityKey; - } - - public CiphertextMessage encrypt(CanonicalRecipientAddress recipient, byte[] paddedBody) { - synchronized (SessionCipher.CIPHER_LOCK) { - SessionCipher sessionCipher = new SessionCipher(); - SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient); - byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody); - - return new CiphertextMessage(sessionContext, ciphertextBody); - } - } - - public byte[] decrypt(CanonicalRecipientAddress recipient, byte[] ciphertext) - throws InvalidMessageException - { - synchronized (SessionCipher.CIPHER_LOCK) { - try { - CiphertextMessage message = new CiphertextMessage(ciphertext); - - int messageVersion = message.getCurrentVersion(); - int senderKeyId = message.getSenderKeyId(); - int receiverKeyId = message.getReceiverKeyId(); - PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes()); - int counter = message.getCounter(); - byte[] body = message.getBody(); - - SessionCipher sessionCipher = new SessionCipher(); - SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret, - localIdentityKey, - recipient, senderKeyId, - receiverKeyId, - nextRemoteKey, - counter, - messageVersion); - - message.verifyMac(sessionContext); - - return sessionCipher.decrypt(sessionContext, body); - } catch (InvalidKeyException e) { - throw new InvalidMessageException(e); - } - } - } -} diff --git a/library/src/org/whispersystems/textsecure/crypto/MessageMac.java b/library/src/org/whispersystems/textsecure/crypto/MessageMac.java deleted file mode 100644 index 88b8506aa3..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/MessageMac.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecure.crypto; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import org.whispersystems.textsecure.util.Hex; - -import android.util.Log; - -public class MessageMac { - - public static final int MAC_LENGTH = 10; - - public static byte[] calculateMac(byte[] message, int offset, int length, SecretKeySpec macKey) { - try { - Mac mac = Mac.getInstance("HmacSHA1"); - mac.init(macKey); - - assert(mac.getMacLength() >= MAC_LENGTH); - - mac.update(message, offset, length); - byte[] macBytes = mac.doFinal(); - byte[] truncatedMacBytes = new byte[MAC_LENGTH]; - System.arraycopy(macBytes, 0, truncatedMacBytes, 0, truncatedMacBytes.length); - - return truncatedMacBytes; - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - - public static void verifyMac(byte[] message, int offset, int length, - byte[] receivedMac, SecretKeySpec macKey) - throws InvalidMacException - { - byte[] localMac = calculateMac(message, offset, length, macKey); - - Log.w("MessageMac", "Local Mac: " + Hex.toString(localMac)); - Log.w("MessageMac", "Remot Mac: " + Hex.toString(receivedMac)); - - if (!Arrays.equals(localMac, receivedMac)) { - throw new InvalidMacException("MAC on message does not match calculated MAC."); - } - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java index 758262610a..92c8cde72e 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java @@ -16,345 +16,31 @@ */ package org.whispersystems.textsecure.crypto; -import android.content.Context; -import android.util.Log; -import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; +import android.content.Context; + import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; -import org.whispersystems.textsecure.storage.InvalidKeyIdException; -import org.whispersystems.textsecure.storage.LocalKeyRecord; -import org.whispersystems.textsecure.storage.RemoteKeyRecord; -import org.whispersystems.textsecure.storage.SessionKey; -import org.whispersystems.textsecure.storage.SessionRecord; -import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.storage.SessionRecordV1; +import org.whispersystems.textsecure.storage.SessionRecordV2; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; +public abstract class SessionCipher { -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; + protected static final Object SESSION_LOCK = new Object(); -/** - * This is where the session encryption magic happens. Implements a compressed version of the OTR protocol. - * - * @author Moxie Marlinspike - */ + public abstract CiphertextMessage encrypt(byte[] paddedMessage); + public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException; -public class SessionCipher { - - public static final Object CIPHER_LOCK = new Object(); - - public static final int CIPHER_KEY_LENGTH = 16; - public static final int MAC_KEY_LENGTH = 20; - - public SessionCipherContext getEncryptionContext(Context context, - MasterSecret masterSecret, - IdentityKeyPair localIdentityKey, - CanonicalRecipientAddress recipient) + public static SessionCipher createFor(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) { - try { - KeyRecords records = getKeyRecords(context, masterSecret, recipient); - int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId(); - int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId(); - int sessionVersion = records.getSessionRecord().getSessionVersion(); - SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, sessionVersion, localIdentityKey, records, localKeyId, remoteKeyId); - PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey(); - int counter = records.getSessionRecord().getCounter(); - - - return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, - nextKey, counter, sessionVersion); - } catch (InvalidKeyIdException e) { - throw new IllegalArgumentException(e); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException(e); - } - } - - public SessionCipherContext getDecryptionContext(Context context, MasterSecret masterSecret, - IdentityKeyPair localIdentityKey, - CanonicalRecipientAddress recipient, - int senderKeyId, int recipientKeyId, - PublicKey nextKey, int counter, - int messageVersion) - throws InvalidMessageException - { - try { - KeyRecords records = getKeyRecords(context, masterSecret, recipient); - - if (messageVersion < records.getSessionRecord().getNegotiatedSessionVersion()) { - throw new InvalidMessageException("Message version: " + messageVersion + - " but negotiated session version: " + - records.getSessionRecord().getNegotiatedSessionVersion()); - } - - SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, - localIdentityKey, records, recipientKeyId, senderKeyId); - - return new SessionCipherContext(records, sessionKey, senderKeyId, - recipientKeyId, nextKey, counter, - messageVersion); - } catch (InvalidKeyIdException e) { - throw new InvalidMessageException(e); - } catch (InvalidKeyException e) { - throw new InvalidMessageException(e); - } - } - - public byte[] encrypt(SessionCipherContext context, byte[] paddedMessageBody) { - Log.w("SessionCipher", "Encrypting message..."); - try { - byte[]cipherText = getCiphertext(paddedMessageBody, context.getSessionKey().getCipherKey(), context.getSessionRecord().getCounter()); - - context.getSessionRecord().setSessionKey(context.getSessionKey()); - context.getSessionRecord().incrementCounter(); - context.getSessionRecord().save(); - - return cipherText; - } catch (IllegalBlockSizeException e) { - throw new IllegalArgumentException(e); - } catch (BadPaddingException e) { - throw new IllegalArgumentException(e); - } - } - - public byte[] decrypt(SessionCipherContext context, byte[] decodedCiphertext) - throws InvalidMessageException - { - Log.w("SessionCipher", "Decrypting message..."); - try { - byte[] plaintextWithPadding = getPlaintext(decodedCiphertext, - context.getSessionKey().getCipherKey(), - context.getCounter()); - - context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey()); - context.getRemoteKeyRecord().save(); - - context.getLocalKeyRecord().advanceKeyIfNecessary(context.getRecipientKeyId()); - context.getLocalKeyRecord().save(); - - context.getSessionRecord().setSessionKey(context.getSessionKey()); - context.getSessionRecord().setPrekeyBundleRequired(false); - context.getSessionRecord().save(); - - return plaintextWithPadding; - } catch (IllegalBlockSizeException e) { - throw new InvalidMessageException("assert", e); - } catch (BadPaddingException e) { - throw new InvalidMessageException("assert", e); - } - } - - private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) - throws IllegalBlockSizeException, BadPaddingException - { - Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter); - return cipher.doFinal(cipherText); - } - - private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) - throws IllegalBlockSizeException, BadPaddingException - { - Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter); - return cipher.doFinal(message); - } - - private Cipher getCipher(int mode, SecretKeySpec key, int counter) { - try { - Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); - - byte[] ivBytes = new byte[16]; - Conversions.mediumToByteArray(ivBytes, 0, counter); - - IvParameterSpec iv = new IvParameterSpec(ivBytes); - cipher.init(mode, key, iv); - - return cipher; - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("AES Not Supported!"); - } catch (NoSuchPaddingException e) { - throw new IllegalArgumentException("NoPadding Not Supported!"); - } catch (java.security.InvalidKeyException e) { - Log.w("SessionCipher", e); - throw new IllegalArgumentException("Invaid Key?"); - } catch (InvalidAlgorithmParameterException e) { - Log.w("SessionCipher", e); - throw new IllegalArgumentException("Bad IV?"); - } - } - - private SessionKey getSessionKey(MasterSecret masterSecret, int mode, - int messageVersion, - IdentityKeyPair localIdentityKey, - KeyRecords records, - int localKeyId, int remoteKeyId) - throws InvalidKeyIdException, InvalidKeyException - { - Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId); - SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId); - - if (sessionKey != null) - return sessionKey; - - DerivedSecrets derivedSecrets = calculateSharedSecret(messageVersion, mode, localIdentityKey, - records, localKeyId, remoteKeyId); - - return new SessionKey(mode, localKeyId, remoteKeyId, derivedSecrets.getCipherKey(), - derivedSecrets.getMacKey(), masterSecret); - } - - private DerivedSecrets calculateSharedSecret(int messageVersion, int mode, - IdentityKeyPair localIdentityKey, - KeyRecords records, - int localKeyId, int remoteKeyId) - throws InvalidKeyIdException, InvalidKeyException - { - KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId); - ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); - IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey(); - boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId); - - isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd); - - if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) && - messageVersion >= CiphertextMessage.DHE3_INTRODUCED_VERSION) - { - return SharedSecretCalculator.calculateSharedSecret(isLowEnd, - localKeyPair, localKeyId, localIdentityKey, - remoteKey, remoteKeyId, remoteIdentityKey); + if (SessionRecordV2.hasSession(context, masterSecret, recipient)) { + return new SessionCipherV2(context, masterSecret, recipient); + } else if (SessionRecordV1.hasSession(context, recipient)) { + return new SessionCipherV1(context, masterSecret, recipient); } else { - return SharedSecretCalculator.calculateSharedSecret(messageVersion, isLowEnd, - localKeyPair, localKeyId, - remoteKey, remoteKeyId); + throw new AssertionError("Attempt to initialize cipher for non-existing session."); } } - private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId) - throws InvalidKeyIdException - { - ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey(); - ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); - - return localPublic.compareTo(remotePublic) < 0; - } - - private boolean isInitiallyExchangedKeys(KeyRecords records, int localKeyId, int remoteKeyId) - throws InvalidKeyIdException - { - byte[] localFingerprint = records.getSessionRecord().getLocalFingerprint(); - byte[] remoteFingerprint = records.getSessionRecord().getRemoteFingerprint(); - - return Arrays.equals(localFingerprint, records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getFingerprintBytes()) && - Arrays.equals(remoteFingerprint, records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getFingerprintBytes()); - } - - private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret, - CanonicalRecipientAddress recipient) - { - LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient); - RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient); - SessionRecord sessionRecord = new SessionRecord(context, masterSecret, recipient); - return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord); - } - - private static class KeyRecords { - - private final LocalKeyRecord localKeyRecord; - private final RemoteKeyRecord remoteKeyRecord; - private final SessionRecord sessionRecord; - - public KeyRecords(LocalKeyRecord localKeyRecord, RemoteKeyRecord remoteKeyRecord, SessionRecord sessionRecord) { - this.localKeyRecord = localKeyRecord; - this.remoteKeyRecord = remoteKeyRecord; - this.sessionRecord = sessionRecord; - } - - private LocalKeyRecord getLocalKeyRecord() { - return localKeyRecord; - } - - private RemoteKeyRecord getRemoteKeyRecord() { - return remoteKeyRecord; - } - - private SessionRecord getSessionRecord() { - return sessionRecord; - } - } - - public static class SessionCipherContext { - - private final LocalKeyRecord localKeyRecord; - private final RemoteKeyRecord remoteKeyRecord; - private final SessionRecord sessionRecord; - private final SessionKey sessionKey; - private final int senderKeyId; - private final int recipientKeyId; - private final PublicKey nextKey; - private final int counter; - private final int messageVersion; - - public SessionCipherContext(KeyRecords records, - SessionKey sessionKey, - int senderKeyId, - int receiverKeyId, - PublicKey nextKey, - int counter, - int messageVersion) - { - this.localKeyRecord = records.getLocalKeyRecord(); - this.remoteKeyRecord = records.getRemoteKeyRecord(); - this.sessionRecord = records.getSessionRecord(); - this.sessionKey = sessionKey; - this.senderKeyId = senderKeyId; - this.recipientKeyId = receiverKeyId; - this.nextKey = nextKey; - this.counter = counter; - this.messageVersion = messageVersion; - } - - public LocalKeyRecord getLocalKeyRecord() { - return localKeyRecord; - } - - public RemoteKeyRecord getRemoteKeyRecord() { - return remoteKeyRecord; - } - - public SessionRecord getSessionRecord() { - return sessionRecord; - } - - public SessionKey getSessionKey() { - return sessionKey; - } - - public PublicKey getNextKey() { - return nextKey; - } - - public int getCounter() { - return counter; - } - - public int getSenderKeyId() { - return senderKeyId; - } - - public int getRecipientKeyId() { - return recipientKeyId; - } - - public int getMessageVersion() { - return messageVersion; - } - } - -} +} \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java new file mode 100644 index 0000000000..bebdf45872 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV1.java @@ -0,0 +1,325 @@ +package org.whispersystems.textsecure.crypto; + +import android.content.Context; +import android.util.Log; + +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; +import org.whispersystems.textsecure.crypto.kdf.NKDF; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; +import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV1; +import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; +import org.whispersystems.textsecure.storage.InvalidKeyIdException; +import org.whispersystems.textsecure.storage.LocalKeyRecord; +import org.whispersystems.textsecure.storage.RemoteKeyRecord; +import org.whispersystems.textsecure.storage.SessionKey; +import org.whispersystems.textsecure.storage.SessionRecordV1; +import org.whispersystems.textsecure.util.Conversions; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class SessionCipherV1 extends SessionCipher { + + private final Context context; + private final MasterSecret masterSecret; + private final CanonicalRecipientAddress recipient; + + public SessionCipherV1(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + this.context = context; + this.masterSecret = masterSecret; + this.recipient = recipient; + } + + public CiphertextMessage encrypt(byte[] paddedMessageBody) { + synchronized (SESSION_LOCK) { + SessionCipherContext encryptionContext = getEncryptionContext(); + byte[] cipherText = getCiphertext(paddedMessageBody, + encryptionContext.getSessionKey().getCipherKey(), + encryptionContext.getSessionRecord().getCounter()); + + encryptionContext.getSessionRecord().setSessionKey(encryptionContext.getSessionKey()); + encryptionContext.getSessionRecord().incrementCounter(); + encryptionContext.getSessionRecord().save(); + + return new WhisperMessageV1(encryptionContext, cipherText); + } + } + + public byte[] decrypt(byte[] decodedCiphertext) throws InvalidMessageException { + synchronized (SESSION_LOCK) { + WhisperMessageV1 message = new WhisperMessageV1(decodedCiphertext); + SessionCipherContext decryptionContext = getDecryptionContext(message); + + message.verifyMac(decryptionContext); + + byte[] plaintextWithPadding = getPlaintext(message.getBody(), + decryptionContext.getSessionKey().getCipherKey(), + decryptionContext.getCounter()); + + decryptionContext.getRemoteKeyRecord().updateCurrentRemoteKey(decryptionContext.getNextKey()); + decryptionContext.getRemoteKeyRecord().save(); + + decryptionContext.getLocalKeyRecord().advanceKeyIfNecessary(decryptionContext.getRecipientKeyId()); + decryptionContext.getLocalKeyRecord().save(); + + decryptionContext.getSessionRecord().setSessionKey(decryptionContext.getSessionKey()); + decryptionContext.getSessionRecord().save(); + + return plaintextWithPadding; + } + } + + private SessionCipherContext getEncryptionContext() { + try { + KeyRecords records = getKeyRecords(context, masterSecret, recipient); + int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId(); + int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId(); + int sessionVersion = records.getSessionRecord().getSessionVersion(); + SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, + records, localKeyId, remoteKeyId); + PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey(); + int counter = records.getSessionRecord().getCounter(); + + + return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, + nextKey, counter, sessionVersion); + } catch (InvalidKeyIdException e) { + throw new IllegalArgumentException(e); + } catch (InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + } + + public SessionCipherContext getDecryptionContext(WhisperMessageV1 message) + throws InvalidMessageException + { + try { + KeyRecords records = getKeyRecords(context, masterSecret, recipient); + int messageVersion = message.getCurrentVersion(); + int recipientKeyId = message.getReceiverKeyId(); + int senderKeyId = message.getSenderKeyId(); + PublicKey nextKey = new PublicKey(message.getNextKeyBytes()); + int counter = message.getCounter(); + + if (messageVersion < records.getSessionRecord().getSessionVersion()) { + throw new InvalidMessageException("Message version: " + messageVersion + + " but negotiated session version: " + + records.getSessionRecord().getSessionVersion()); + } + + SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, + records, recipientKeyId, senderKeyId); + + return new SessionCipherContext(records, sessionKey, senderKeyId, + recipientKeyId, nextKey, counter, + messageVersion); + } catch (InvalidKeyIdException e) { + throw new InvalidMessageException(e); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + + private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) { + try { + Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter); + return cipher.doFinal(message); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } + } + + private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) { + try { + Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter); + return cipher.doFinal(cipherText); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } + } + + private Cipher getCipher(int mode, SecretKeySpec key, int counter) { + try { + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + + byte[] ivBytes = new byte[16]; + Conversions.mediumToByteArray(ivBytes, 0, counter); + + IvParameterSpec iv = new IvParameterSpec(ivBytes); + cipher.init(mode, key, iv); + + return cipher; + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("AES Not Supported!"); + } catch (NoSuchPaddingException e) { + throw new IllegalArgumentException("NoPadding Not Supported!"); + } catch (java.security.InvalidKeyException e) { + Log.w("SessionCipher", e); + throw new IllegalArgumentException("Invaid Key?"); + } catch (InvalidAlgorithmParameterException e) { + Log.w("SessionCipher", e); + throw new IllegalArgumentException("Bad IV?"); + } + } + + private SessionKey getSessionKey(MasterSecret masterSecret, int mode, + KeyRecords records, + int localKeyId, int remoteKeyId) + throws InvalidKeyIdException, InvalidKeyException + { + Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId); + SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId); + + if (sessionKey != null) + return sessionKey; + + DerivedSecrets derivedSecrets = calculateSharedSecret(mode, records, localKeyId, remoteKeyId); + + return new SessionKey(mode, localKeyId, remoteKeyId, derivedSecrets.getCipherKey(), + derivedSecrets.getMacKey(), masterSecret); + } + + private DerivedSecrets calculateSharedSecret(int mode, KeyRecords records, + int localKeyId, int remoteKeyId) + throws InvalidKeyIdException, InvalidKeyException + { + NKDF kdf = new NKDF(); + KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId); + ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); + byte[] sharedSecret = Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey()); + boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId); + + isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd); + + return kdf.deriveSecrets(sharedSecret, isLowEnd); + } + + private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId) + throws InvalidKeyIdException + { + ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey(); + ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); + + return localPublic.compareTo(remotePublic) < 0; + } + + private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient); + RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient); + SessionRecordV1 sessionRecord = new SessionRecordV1(context, masterSecret, recipient); + return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord); + } + + private static class KeyRecords { + + private final LocalKeyRecord localKeyRecord; + private final RemoteKeyRecord remoteKeyRecord; + private final SessionRecordV1 sessionRecord; + + public KeyRecords(LocalKeyRecord localKeyRecord, + RemoteKeyRecord remoteKeyRecord, + SessionRecordV1 sessionRecord) + { + this.localKeyRecord = localKeyRecord; + this.remoteKeyRecord = remoteKeyRecord; + this.sessionRecord = sessionRecord; + } + + private LocalKeyRecord getLocalKeyRecord() { + return localKeyRecord; + } + + private RemoteKeyRecord getRemoteKeyRecord() { + return remoteKeyRecord; + } + + private SessionRecordV1 getSessionRecord() { + return sessionRecord; + } + } + + public static class SessionCipherContext { + + private final LocalKeyRecord localKeyRecord; + private final RemoteKeyRecord remoteKeyRecord; + private final SessionRecordV1 sessionRecord; + private final SessionKey sessionKey; + private final int senderKeyId; + private final int recipientKeyId; + private final PublicKey nextKey; + private final int counter; + private final int messageVersion; + + public SessionCipherContext(KeyRecords records, + SessionKey sessionKey, + int senderKeyId, + int receiverKeyId, + PublicKey nextKey, + int counter, + int messageVersion) + { + this.localKeyRecord = records.getLocalKeyRecord(); + this.remoteKeyRecord = records.getRemoteKeyRecord(); + this.sessionRecord = records.getSessionRecord(); + this.sessionKey = sessionKey; + this.senderKeyId = senderKeyId; + this.recipientKeyId = receiverKeyId; + this.nextKey = nextKey; + this.counter = counter; + this.messageVersion = messageVersion; + } + + public LocalKeyRecord getLocalKeyRecord() { + return localKeyRecord; + } + + public RemoteKeyRecord getRemoteKeyRecord() { + return remoteKeyRecord; + } + + public SessionRecordV1 getSessionRecord() { + return sessionRecord; + } + + public SessionKey getSessionKey() { + return sessionKey; + } + + public PublicKey getNextKey() { + return nextKey; + } + + public int getCounter() { + return counter; + } + + public int getSenderKeyId() { + return senderKeyId; + } + + public int getRecipientKeyId() { + return recipientKeyId; + } + + public int getMessageVersion() { + return messageVersion; + } + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java new file mode 100644 index 0000000000..b5fd072681 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipherV2.java @@ -0,0 +1,201 @@ +package org.whispersystems.textsecure.crypto; + +import android.content.Context; +import android.util.Log; +import android.util.Pair; + +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; +import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; +import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2; +import org.whispersystems.textsecure.crypto.ratchet.ChainKey; +import org.whispersystems.textsecure.crypto.ratchet.MessageKeys; +import org.whispersystems.textsecure.crypto.ratchet.RootKey; +import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; +import org.whispersystems.textsecure.storage.SessionRecordV2; +import org.whispersystems.textsecure.util.Conversions; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class SessionCipherV2 extends SessionCipher { + + private final Context context; + private final MasterSecret masterSecret; + private final CanonicalRecipientAddress recipient; + + public SessionCipherV2(Context context, + MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + this.context = context; + this.masterSecret = masterSecret; + this.recipient = recipient; + } + + @Override + public CiphertextMessage encrypt(byte[] paddedMessage) { + synchronized (SESSION_LOCK) { + SessionRecordV2 sessionRecord = getSessionRecord(); + ChainKey chainKey = sessionRecord.getSenderChainKey(); + MessageKeys messageKeys = chainKey.getMessageKeys(); + ECPublicKey senderEphemeral = sessionRecord.getSenderEphemeral(); + int previousCounter = sessionRecord.getPreviousCounter(); + + byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage); + CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(), + senderEphemeral, chainKey.getIndex(), + previousCounter, ciphertextBody); + + if (sessionRecord.hasPendingPreKey()) { + Pair pendingPreKey = sessionRecord.getPendingPreKey(); + ciphertextMessage = new PreKeyWhisperMessage(pendingPreKey.first, pendingPreKey.second, + sessionRecord.getLocalIdentityKey(), + (WhisperMessageV2) ciphertextMessage); + } + + sessionRecord.setSenderChainKey(chainKey.getNextChainKey()); + sessionRecord.save(); + + return ciphertextMessage; + } + } + + @Override + public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException { + synchronized (SESSION_LOCK) { + SessionRecordV2 sessionRecord = getSessionRecord(); + WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage); + ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral(); + int counter = ciphertextMessage.getCounter(); + ChainKey chainKey = getOrCreateChainKey(sessionRecord, theirEphemeral); + MessageKeys messageKeys = getOrCreateMessageKeys(sessionRecord, theirEphemeral, + chainKey, counter); + + ciphertextMessage.verifyMac(messageKeys.getMacKey()); + + byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody()); + + sessionRecord.clearPendingPreKey(); + sessionRecord.save(); + + return plaintext; + } + } + + private ChainKey getOrCreateChainKey(SessionRecordV2 sessionRecord, ECPublicKey theirEphemeral) + throws InvalidMessageException + { + try { + if (sessionRecord.hasReceiverChain(theirEphemeral)) { + return sessionRecord.getReceiverChainKey(theirEphemeral); + } else { + RootKey rootKey = sessionRecord.getRootKey(); + ECKeyPair ourEphemeral = sessionRecord.getSenderEphemeralPair(); + Pair receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral); + ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE); + Pair senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral); + + sessionRecord.setRootKey(senderChain.first); + sessionRecord.addReceiverChain(theirEphemeral, receiverChain.second); + sessionRecord.setPreviousCounter(sessionRecord.getSenderChainKey().getIndex()-1); + sessionRecord.setSenderChain(ourNewEphemeral, senderChain.second); + + return receiverChain.second; + } + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + + private MessageKeys getOrCreateMessageKeys(SessionRecordV2 sessionRecord, + ECPublicKey theirEphemeral, + ChainKey chainKey, int counter) + throws InvalidMessageException + { + if (chainKey.getIndex() > counter) { + if (sessionRecord.hasMessageKeys(theirEphemeral, counter)) { + return sessionRecord.removeMessageKeys(theirEphemeral, counter); + } else { + throw new InvalidMessageException("Received message with old counter!"); + } + } + + if (chainKey.getIndex() - counter > 500) { + throw new InvalidMessageException("Over 500 messages into the future!"); + } + + while (chainKey.getIndex() < counter) { + MessageKeys messageKeys = chainKey.getMessageKeys(); + sessionRecord.setMessageKeys(theirEphemeral, messageKeys); + chainKey = chainKey.getNextChainKey(); + } + + sessionRecord.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey()); + return chainKey.getMessageKeys(); + } + + private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) { + try { + Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, + messageKeys.getCipherKey(), + messageKeys.getCounter()); + + return cipher.doFinal(plaintext); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } + } + + private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) { + try { + Cipher cipher = getCipher(Cipher.DECRYPT_MODE, + messageKeys.getCipherKey(), + messageKeys.getCounter()); + return cipher.doFinal(cipherText); + } catch (IllegalBlockSizeException e) { + throw new AssertionError(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } + } + + private Cipher getCipher(int mode, SecretKeySpec key, int counter) { + try { + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + + byte[] ivBytes = new byte[16]; + Conversions.intToByteArray(ivBytes, 0, counter); + + IvParameterSpec iv = new IvParameterSpec(ivBytes); + cipher.init(mode, key, iv); + + return cipher; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (NoSuchPaddingException e) { + throw new AssertionError(e); + } catch (java.security.InvalidKeyException e) { + throw new AssertionError(e); + } catch (InvalidAlgorithmParameterException e) { + throw new AssertionError(e); + } + } + + + private SessionRecordV2 getSessionRecord() { + return new SessionRecordV2(context, masterSecret, recipient); + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/SharedSecretCalculator.java b/library/src/org/whispersystems/textsecure/crypto/SharedSecretCalculator.java deleted file mode 100644 index 6fd3f9956a..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/SharedSecretCalculator.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.whispersystems.textsecure.crypto; - -import android.util.Log; - -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; -import org.whispersystems.textsecure.crypto.kdf.HKDF; -import org.whispersystems.textsecure.crypto.kdf.KDF; -import org.whispersystems.textsecure.crypto.kdf.NKDF; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.util.Conversions; - -import java.util.LinkedList; -import java.util.List; - -public class SharedSecretCalculator { - - public static DerivedSecrets calculateSharedSecret(boolean isLowEnd, KeyPair localKeyPair, - int localKeyId, - IdentityKeyPair localIdentityKeyPair, - ECPublicKey remoteKey, - int remoteKeyId, - IdentityKey remoteIdentityKey) - throws InvalidKeyException - { - Log.w("SharedSecretCalculator", "Calculating shared secret with 3DHE agreement..."); - KDF kdf = new HKDF(); - List results = new LinkedList(); - - if (isSmaller(localKeyPair.getPublicKey().getKey(), remoteKey)) { - results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey())); - results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(), - localKeyPair.getPrivateKey())); - } else { - results.add(Curve.calculateAgreement(remoteIdentityKey.getPublicKey(), - localKeyPair.getPrivateKey())); - results.add(Curve.calculateAgreement(remoteKey, localIdentityKeyPair.getPrivateKey())); - } - - results.add(Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey())); - - return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId, remoteKeyId)); - } - - public static DerivedSecrets calculateSharedSecret(int messageVersion, boolean isLowEnd, - KeyPair localKeyPair, int localKeyId, - ECPublicKey remoteKey, int remoteKeyId) - throws InvalidKeyException - { - Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement..."); - KDF kdf; - - if (messageVersion >= CiphertextMessage.DHE3_INTRODUCED_VERSION) kdf = new HKDF(); - else kdf = new NKDF(); - - Log.w("SharedSecretCalculator", "Using kdf: " + kdf); - - List results = new LinkedList(); - results.add(Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey())); - - return kdf.deriveSecrets(results, isLowEnd, getInfo(localKeyId, remoteKeyId)); - } - - private static byte[] getInfo(int localKeyId, int remoteKeyId) { - byte[] info = new byte[3 * 2]; - - if (localKeyId < remoteKeyId) { - Conversions.mediumToByteArray(info, 0, localKeyId); - Conversions.mediumToByteArray(info, 3, remoteKeyId); - } else { - Conversions.mediumToByteArray(info, 0, remoteKeyId); - Conversions.mediumToByteArray(info, 3, localKeyId); - } - - return info; - } - - private static boolean isSmaller(ECPublicKey localPublic, - ECPublicKey remotePublic) - { - return localPublic.compareTo(remotePublic) < 0; - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java index 9cd221335f..aa9381969a 100644 --- a/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/Curve.java @@ -36,10 +36,10 @@ public class Curve { } public static ECKeyPair generateKeyPairForSession(int messageVersion) { - if (messageVersion >= CiphertextMessage.CURVE25519_INTRODUCED_VERSION) { - return generateKeyPairForType(DJB_TYPE); - } else { + if (messageVersion <= CiphertextMessage.LEGACY_VERSION) { return generateKeyPairForType(NIST_TYPE); + } else { + return generateKeyPairForType(DJB_TYPE); } } diff --git a/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java b/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java index 2fc454c73c..a592080eaa 100644 --- a/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java +++ b/library/src/org/whispersystems/textsecure/crypto/ecc/ECKeyPair.java @@ -27,7 +27,6 @@ public class ECKeyPair { this.privateKey = privateKey; } - public ECPublicKey getPublicKey() { return publicKey; } diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java b/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java index e07e4baa4c..d6cea242f1 100644 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java +++ b/library/src/org/whispersystems/textsecure/crypto/kdf/HKDF.java @@ -26,50 +26,38 @@ import java.util.List; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -public class HKDF extends KDF { +public class HKDF { private static final int HASH_OUTPUT_SIZE = 32; - private static final int KEY_MATERIAL_SIZE = 72; + private static final int KEY_MATERIAL_SIZE = 64; private static final int CIPHER_KEYS_OFFSET = 0; private static final int MAC_KEYS_OFFSET = 32; - @Override - public DerivedSecrets deriveSecrets(List sharedSecret, - boolean isLowEnd, byte[] info) - { - byte[] inputKeyMaterial = concatenateSharedSecrets(sharedSecret); - byte[] salt = new byte[HASH_OUTPUT_SIZE]; + public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] info) { + byte[] salt = new byte[HASH_OUTPUT_SIZE]; + return deriveSecrets(inputKeyMaterial, salt, info); + } + + public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info) { byte[] prk = extract(salt, inputKeyMaterial); byte[] okm = expand(prk, info, KEY_MATERIAL_SIZE); - SecretKeySpec cipherKey = deriveCipherKey(okm, isLowEnd); - SecretKeySpec macKey = deriveMacKey(okm, isLowEnd); + SecretKeySpec cipherKey = deriveCipherKey(okm); + SecretKeySpec macKey = deriveMacKey(okm); return new DerivedSecrets(cipherKey, macKey); } - private SecretKeySpec deriveCipherKey(byte[] okm, boolean isLowEnd) { - byte[] cipherKey = new byte[16]; - - if (isLowEnd) { - System.arraycopy(okm, CIPHER_KEYS_OFFSET + 0, cipherKey, 0, cipherKey.length); - } else { - System.arraycopy(okm, CIPHER_KEYS_OFFSET + 16, cipherKey, 0, cipherKey.length); - } - + private SecretKeySpec deriveCipherKey(byte[] okm) { + byte[] cipherKey = new byte[32]; + System.arraycopy(okm, CIPHER_KEYS_OFFSET, cipherKey, 0, cipherKey.length); return new SecretKeySpec(cipherKey, "AES"); } - private SecretKeySpec deriveMacKey(byte[] okm, boolean isLowEnd) { - byte[] macKey = new byte[20]; - - if (isLowEnd) { - System.arraycopy(okm, MAC_KEYS_OFFSET + 0, macKey, 0, macKey.length); - } else { - System.arraycopy(okm, MAC_KEYS_OFFSET + 20, macKey, 0, macKey.length); - } - + private SecretKeySpec deriveMacKey(byte[] okm) { + byte[] macKey = new byte[32]; + System.arraycopy(okm, MAC_KEYS_OFFSET, macKey, 0, macKey.length); return new SecretKeySpec(macKey, "HmacSHA1"); } @@ -96,7 +84,9 @@ public class HKDF extends KDF { mac.init(new SecretKeySpec(prk, "HmacSHA256")); mac.update(mixin); - mac.update(info); + if (info != null) { + mac.update(info); + } mac.update((byte)i); byte[] stepResult = mac.doFinal(); diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java b/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java deleted file mode 100644 index 66e9b86e59..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/KDF.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.whispersystems.textsecure.crypto.kdf; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.util.LinkedList; -import java.util.List; - -public abstract class KDF { - - public abstract DerivedSecrets deriveSecrets(List sharedSecret, - boolean isLowEnd, byte[] info); - - protected byte[] concatenateSharedSecrets(List sharedSecrets) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - for (byte[] sharedSecret : sharedSecrets) { - baos.write(sharedSecret); - } - - return baos.toByteArray(); - } catch (IOException e) { - throw new AssertionError(e); - } - } - -} diff --git a/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java b/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java index afde62a1a4..1ae5913744 100644 --- a/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java +++ b/library/src/org/whispersystems/textsecure/crypto/kdf/NKDF.java @@ -23,15 +23,15 @@ import org.whispersystems.textsecure.util.Conversions; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.List; import javax.crypto.spec.SecretKeySpec; -public class NKDF extends KDF { +public class NKDF { - @Override - public DerivedSecrets deriveSecrets(List sharedSecret, - boolean isLowEnd, byte[] info) + public static final int LEGACY_CIPHER_KEY_LENGTH = 16; + public static final int LEGACY_MAC_KEY_LENGTH = 20; + + public DerivedSecrets deriveSecrets(byte[] sharedSecret, boolean isLowEnd) { SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret); SecretKeySpec macKey = deriveMacSecret(cipherKey); @@ -39,15 +39,14 @@ public class NKDF extends KDF { return new DerivedSecrets(cipherKey, macKey); } - private SecretKeySpec deriveCipherSecret(boolean isLowEnd, List sharedSecret) { - byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret); - byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2); - byte[] cipherSecret = new byte[16]; + private SecretKeySpec deriveCipherSecret(boolean isLowEnd, byte[] sharedSecret) { + byte[] derivedBytes = deriveBytes(sharedSecret, LEGACY_CIPHER_KEY_LENGTH * 2); + byte[] cipherSecret = new byte[LEGACY_CIPHER_KEY_LENGTH]; if (isLowEnd) { - System.arraycopy(derivedBytes, 16, cipherSecret, 0, 16); + System.arraycopy(derivedBytes, LEGACY_CIPHER_KEY_LENGTH, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH); } else { - System.arraycopy(derivedBytes, 0, cipherSecret, 0, 16); + System.arraycopy(derivedBytes, 0, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH); } return new SecretKeySpec(cipherSecret, "AES"); @@ -84,6 +83,4 @@ public class NKDF extends KDF { return md.digest(); } - - } diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java index e2322e694e..b431b39ee1 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java @@ -1,145 +1,18 @@ package org.whispersystems.textsecure.crypto.protocol; -import org.whispersystems.textsecure.crypto.InvalidMacException; -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.MessageMac; -import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.crypto.SessionCipher; -import org.whispersystems.textsecure.util.Conversions; +public interface CiphertextMessage { -public class CiphertextMessage { + public static final int LEGACY_VERSION = 1; + public static final int CURRENT_VERSION = 2; - public static final int SUPPORTED_VERSION = 2; - public static final int DHE3_INTRODUCED_VERSION = 2; - public static final int CURVE25519_INTRODUCED_VERSION = 2; + public static final int LEGACY_WHISPER_TYPE = 1; + public static final int CURRENT_WHISPER_TYPE = 2; + public static final int PREKEY_WHISPER_TYPE = 3; - static final int VERSION_LENGTH = 1; - private static final int SENDER_KEY_ID_LENGTH = 3; - private static final int RECEIVER_KEY_ID_LENGTH = 3; - private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; - private static final int COUNTER_LENGTH = 3; - private static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + - RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + - NEXT_KEY_LENGTH; + // This should be the worst case (worse than V2). So not always accurate, but good enough for padding. + public static final int ENCRYPTED_MESSAGE_OVERHEAD = WhisperMessageV1.ENCRYPTED_MESSAGE_OVERHEAD; - static final int VERSION_OFFSET = 0; - private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; - private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; - private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; - private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; - private static final int BODY_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; + public byte[] serialize(); + public int getType(); - public static final int ENCRYPTED_MESSAGE_OVERHEAD = HEADER_LENGTH + MessageMac.MAC_LENGTH; - - private final byte[] ciphertext; - - public CiphertextMessage(SessionCipher.SessionCipherContext sessionContext, byte[] ciphertextBody) { - this.ciphertext = new byte[HEADER_LENGTH + ciphertextBody.length + MessageMac.MAC_LENGTH]; - setVersion(sessionContext.getMessageVersion(), SUPPORTED_VERSION); - setSenderKeyId(sessionContext.getSenderKeyId()); - setReceiverKeyId(sessionContext.getRecipientKeyId()); - setNextKeyBytes(sessionContext.getNextKey().serialize()); - setCounter(sessionContext.getCounter()); - setBody(ciphertextBody); - setMac(MessageMac.calculateMac(ciphertext, 0, ciphertext.length - MessageMac.MAC_LENGTH, - sessionContext.getSessionKey().getMacKey())); - } - - public CiphertextMessage(byte[] ciphertext) throws InvalidMessageException { - this.ciphertext = ciphertext; - - if (ciphertext.length < HEADER_LENGTH) { - throw new InvalidMessageException("Not long enough for ciphertext header!"); - } - - if (getCurrentVersion() > SUPPORTED_VERSION) { - throw new InvalidMessageException("Unspported version: " + getCurrentVersion()); - } - } - - public void setVersion(int current, int supported) { - ciphertext[VERSION_OFFSET] = Conversions.intsToByteHighAndLow(current, supported); - } - - public int getCurrentVersion() { - return Conversions.highBitsToInt(ciphertext[VERSION_OFFSET]); - } - - public int getSupportedVersion() { - return Conversions.lowBitsToInt(ciphertext[VERSION_OFFSET]); - } - - public void setSenderKeyId(int senderKeyId) { - Conversions.mediumToByteArray(ciphertext, SENDER_KEY_ID_OFFSET, senderKeyId); - } - - public int getSenderKeyId() { - return Conversions.byteArrayToMedium(ciphertext, SENDER_KEY_ID_OFFSET); - } - - public void setReceiverKeyId(int receiverKeyId) { - Conversions.mediumToByteArray(ciphertext, RECEIVER_KEY_ID_OFFSET, receiverKeyId); - } - - public int getReceiverKeyId() { - return Conversions.byteArrayToMedium(ciphertext, RECEIVER_KEY_ID_OFFSET); - } - - public void setNextKeyBytes(byte[] nextKey) { - assert(nextKey.length == NEXT_KEY_LENGTH); - System.arraycopy(nextKey, 0, ciphertext, NEXT_KEY_OFFSET, nextKey.length); - } - - public byte[] getNextKeyBytes() { - byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH]; - System.arraycopy(ciphertext, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length); - - return nextKeyBytes; - } - - public void setCounter(int counter) { - Conversions.mediumToByteArray(ciphertext, COUNTER_OFFSET, counter); - } - - public int getCounter() { - return Conversions.byteArrayToMedium(ciphertext, COUNTER_OFFSET); - } - - public void setBody(byte[] body) { - System.arraycopy(body, 0, ciphertext, BODY_OFFSET, body.length); - } - - public byte[] getBody() { - byte[] body = new byte[ciphertext.length - HEADER_LENGTH - MessageMac.MAC_LENGTH]; - System.arraycopy(ciphertext, BODY_OFFSET, body, 0, body.length); - - return body; - } - - public void setMac(byte[] mac) { - System.arraycopy(mac, 0, ciphertext, ciphertext.length-mac.length, mac.length); - } - - public byte[] getMac() { - byte[] mac = new byte[MessageMac.MAC_LENGTH]; - System.arraycopy(ciphertext, ciphertext.length-mac.length, mac, 0, mac.length); - - return mac; - } - - public byte[] serialize() { - return ciphertext; - } - - public void verifyMac(SessionCipher.SessionCipherContext sessionContext) - throws InvalidMessageException - { - try { - MessageMac.verifyMac(this.ciphertext, 0, this.ciphertext.length - MessageMac.MAC_LENGTH, - getMac(), sessionContext.getSessionKey().getMacKey()); - } catch (InvalidMacException e) { - throw new InvalidMessageException(e); - } - } - -} +} \ No newline at end of file diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java deleted file mode 100644 index 741f0979a0..0000000000 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecure.crypto.protocol; - -import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.util.Conversions; - -/** - * Class responsible for parsing and constructing PreKeyBundle messages. - * - * The class takes an existing encrypted message and bundles in the necessary - * additional information for a prekeybundle, namely the addition of the local - * identity key. - */ -public class PreKeyBundleMessage { - - public static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION; - - private static final int VERSION_LENGTH = CiphertextMessage.VERSION_LENGTH; - private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE; - - private static final int VERSION_OFFSET = CiphertextMessage.VERSION_OFFSET; - private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + VERSION_LENGTH; - - private final byte[] messageBytes; - private final CiphertextMessage bundledMessage; - private final IdentityKey identityKey; - - public PreKeyBundleMessage(byte[] messageBytes) - throws InvalidVersionException, InvalidKeyException - { - try { - this.messageBytes = messageBytes; - int messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]); - - if (messageVersion > CiphertextMessage.SUPPORTED_VERSION) - throw new InvalidVersionException("Key exchange with version: " + messageVersion); - - this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET); - byte[] bundledMessageBytes = new byte[messageBytes.length - IDENTITY_KEY_LENGTH]; - - bundledMessageBytes[VERSION_OFFSET] = this.messageBytes[VERSION_OFFSET]; - System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes, - VERSION_OFFSET+VERSION_LENGTH, bundledMessageBytes.length-VERSION_LENGTH); - - this.bundledMessage = new CiphertextMessage(bundledMessageBytes); - } catch (InvalidMessageException e) { - throw new InvalidKeyException(e); - } - } - - public PreKeyBundleMessage(CiphertextMessage bundledMessage, IdentityKey identityKey) { - this.bundledMessage = bundledMessage; - this.identityKey = identityKey; - this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.serialize().length]; - - byte[] bundledMessageBytes = bundledMessage.serialize(); - byte[] identityKeyBytes = identityKey.serialize(); - - messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET]; - System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length); - System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, - messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, - bundledMessageBytes.length-VERSION_LENGTH); - } - - public byte[] serialize() { - return this.messageBytes; - } - - public int getSupportedVersion() { - return bundledMessage.getSupportedVersion(); - } - - public IdentityKey getIdentityKey() { - return identityKey; - } - - public PublicKey getPublicKey() throws InvalidKeyException { - return new PublicKey(bundledMessage.getNextKeyBytes()); - } - - public CiphertextMessage getBundledMessage() { - return bundledMessage; - } - - public int getPreKeyId() { - return bundledMessage.getReceiverKeyId(); - } -} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java new file mode 100644 index 0000000000..bd0226ce8c --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyWhisperMessage.java @@ -0,0 +1,104 @@ +package org.whispersystems.textsecure.crypto.protocol; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Util; + +public class PreKeyWhisperMessage implements CiphertextMessage { + + private final int version; + private final int preKeyId; + private final ECPublicKey baseKey; + private final IdentityKey identityKey; + private final WhisperMessageV2 message; + private final byte[] serialized; + + public PreKeyWhisperMessage(byte[] serialized) + throws InvalidMessageException, InvalidVersionException + { + try { + this.version = Conversions.lowBitsToInt(serialized[0]); + + if (this.version > CiphertextMessage.CURRENT_VERSION) { + throw new InvalidVersionException("Unknown version: " + this.version); + } + + WhisperProtos.PreKeyWhisperMessage preKeyWhisperMessage + = WhisperProtos.PreKeyWhisperMessage.parseFrom(ByteString.copyFrom(serialized, 1, + serialized.length-1)); + + if (!preKeyWhisperMessage.hasPreKeyId() || + !preKeyWhisperMessage.hasBaseKey() || + !preKeyWhisperMessage.hasIdentityKey() || + !preKeyWhisperMessage.hasMessage()) + { + throw new InvalidMessageException("Incomplete message."); + } + + this.serialized = serialized; + this.preKeyId = preKeyWhisperMessage.getPreKeyId(); + this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0); + this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0)); + this.message = new WhisperMessageV2(preKeyWhisperMessage.getMessage().toByteArray()); + } catch (InvalidProtocolBufferException e) { + throw new InvalidMessageException(e); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + + public PreKeyWhisperMessage(int preKeyId, ECPublicKey baseKey, IdentityKey identityKey, + WhisperMessageV2 message) + { + this.version = CiphertextMessage.CURRENT_VERSION; + this.preKeyId = preKeyId; + this.baseKey = baseKey; + this.identityKey = identityKey; + this.message = message; + + byte[] versionBytes = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, this.version)}; + byte[] messageBytes = WhisperProtos.PreKeyWhisperMessage.newBuilder() + .setPreKeyId(preKeyId) + .setBaseKey(ByteString.copyFrom(baseKey.serialize())) + .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) + .setMessage(ByteString.copyFrom(message.serialize())) + .build().toByteArray(); + + this.serialized = Util.combine(versionBytes, messageBytes); + } + + public IdentityKey getIdentityKey() { + return identityKey; + } + + public int getPreKeyId() { + return preKeyId; + } + + public ECPublicKey getBaseKey() { + return baseKey; + } + + public WhisperMessageV2 getWhisperMessage() { + return message; + } + + @Override + public byte[] serialize() { + return serialized; + } + + @Override + public int getType() { + return CiphertextMessage.PREKEY_WHISPER_TYPE; + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV1.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV1.java new file mode 100644 index 0000000000..7efe8e3b6a --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV1.java @@ -0,0 +1,187 @@ +package org.whispersystems.textsecure.crypto.protocol; + +import android.util.Log; + +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.SessionCipherV1; +import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + + +public class WhisperMessageV1 implements CiphertextMessage{ + + private static final int VERSION_LENGTH = 1; + private static final int SENDER_KEY_ID_LENGTH = 3; + private static final int RECEIVER_KEY_ID_LENGTH = 3; + private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; + private static final int COUNTER_LENGTH = 3; + private static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + + RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + + NEXT_KEY_LENGTH; + private static final int MAC_LENGTH = 10; + + + private static final int VERSION_OFFSET = 0; + private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; + private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; + private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; + private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; + private static final int BODY_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; + + static final int ENCRYPTED_MESSAGE_OVERHEAD = HEADER_LENGTH + MAC_LENGTH; + + private final byte[] ciphertext; + + public WhisperMessageV1(SessionCipherV1.SessionCipherContext sessionContext, + byte[] ciphertextBody) + { + this.ciphertext = new byte[HEADER_LENGTH + ciphertextBody.length + MAC_LENGTH]; + setVersion(sessionContext.getMessageVersion(), CURRENT_VERSION); + setSenderKeyId(sessionContext.getSenderKeyId()); + setReceiverKeyId(sessionContext.getRecipientKeyId()); + setNextKeyBytes(sessionContext.getNextKey().serialize()); + setCounter(sessionContext.getCounter()); + setBody(ciphertextBody); + setMac(calculateMac(sessionContext.getSessionKey().getMacKey(), + ciphertext, 0, ciphertext.length - MAC_LENGTH)); + } + + public WhisperMessageV1(byte[] ciphertext) throws InvalidMessageException { + this.ciphertext = ciphertext; + + if (ciphertext.length < HEADER_LENGTH) { + throw new InvalidMessageException("Not long enough for ciphertext header!"); + } + + if (getCurrentVersion() > LEGACY_VERSION) { + throw new InvalidMessageException("Received non-legacy version: " + getCurrentVersion()); + } + } + + public void setVersion(int current, int supported) { + ciphertext[VERSION_OFFSET] = Conversions.intsToByteHighAndLow(current, supported); + } + + public int getCurrentVersion() { + return Conversions.highBitsToInt(ciphertext[VERSION_OFFSET]); + } + + public int getSupportedVersion() { + return Conversions.lowBitsToInt(ciphertext[VERSION_OFFSET]); + } + + public void setSenderKeyId(int senderKeyId) { + Conversions.mediumToByteArray(ciphertext, SENDER_KEY_ID_OFFSET, senderKeyId); + } + + public int getSenderKeyId() { + return Conversions.byteArrayToMedium(ciphertext, SENDER_KEY_ID_OFFSET); + } + + public void setReceiverKeyId(int receiverKeyId) { + Conversions.mediumToByteArray(ciphertext, RECEIVER_KEY_ID_OFFSET, receiverKeyId); + } + + public int getReceiverKeyId() { + return Conversions.byteArrayToMedium(ciphertext, RECEIVER_KEY_ID_OFFSET); + } + + public void setNextKeyBytes(byte[] nextKey) { + assert(nextKey.length == NEXT_KEY_LENGTH); + System.arraycopy(nextKey, 0, ciphertext, NEXT_KEY_OFFSET, nextKey.length); + } + + public byte[] getNextKeyBytes() { + byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH]; + System.arraycopy(ciphertext, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length); + + return nextKeyBytes; + } + + public void setCounter(int counter) { + Conversions.mediumToByteArray(ciphertext, COUNTER_OFFSET, counter); + } + + public int getCounter() { + return Conversions.byteArrayToMedium(ciphertext, COUNTER_OFFSET); + } + + public void setBody(byte[] body) { + System.arraycopy(body, 0, ciphertext, BODY_OFFSET, body.length); + } + + public byte[] getBody() { + byte[] body = new byte[ciphertext.length - HEADER_LENGTH - MAC_LENGTH]; + System.arraycopy(ciphertext, BODY_OFFSET, body, 0, body.length); + + return body; + } + + public void setMac(byte[] mac) { + System.arraycopy(mac, 0, ciphertext, ciphertext.length-mac.length, mac.length); + } + + public byte[] getMac() { + byte[] mac = new byte[MAC_LENGTH]; + System.arraycopy(ciphertext, ciphertext.length-mac.length, mac, 0, mac.length); + + return mac; + } + + @Override + public byte[] serialize() { + return ciphertext; + } + + @Override + public int getType() { + return CiphertextMessage.LEGACY_WHISPER_TYPE; + } + + public void verifyMac(SessionCipherV1.SessionCipherContext sessionContext) + throws InvalidMessageException + { + verifyMac(sessionContext.getSessionKey().getMacKey(), + this.ciphertext, 0, this.ciphertext.length - MAC_LENGTH, getMac()); + } + + private byte[] calculateMac(SecretKeySpec macKey, byte[] message, int offset, int length) { + try { + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(macKey); + + mac.update(message, offset, length); + byte[] macBytes = mac.doFinal(); + + return Util.trim(macBytes, MAC_LENGTH); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } catch (InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + } + + private void verifyMac(SecretKeySpec macKey, byte[] message, int offset, int length, + byte[] receivedMac) + throws InvalidMessageException + { + byte[] localMac = calculateMac(macKey, message, offset, length); + + Log.w("WhisperMessageV1", "Local Mac: " + Hex.toString(localMac)); + Log.w("WhisperMessageV1", "Remot Mac: " + Hex.toString(receivedMac)); + + if (!Arrays.equals(localMac, receivedMac)) { + throw new InvalidMessageException("MAC on message does not match calculated MAC."); + } + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java new file mode 100644 index 0000000000..af6d972c13 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperMessageV2.java @@ -0,0 +1,132 @@ +package org.whispersystems.textsecure.crypto.protocol; + +import android.util.Log; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage; +import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; + +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class WhisperMessageV2 implements CiphertextMessage { + + private static final int MAC_LENGTH = 8; + + private final ECPublicKey senderEphemeral; + private final int counter; + private final int previousCounter; + private final byte[] ciphertext; + private final byte[] serialized; + + public WhisperMessageV2(byte[] serialized) throws InvalidMessageException { + try { + byte[][] messageParts = Util.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH); + byte version = messageParts[0][0]; + byte[] message = messageParts[1]; + byte[] mac = messageParts[2]; + + if (Conversions.highBitsToInt(version) != CURRENT_VERSION) { + throw new InvalidMessageException("Unknown version: " + Conversions.lowBitsToInt(version)); + } + + WhisperMessage whisperMessage = WhisperMessage.parseFrom(message); + + if (!whisperMessage.hasCiphertext() || + !whisperMessage.hasCounter() || + !whisperMessage.hasEphemeralKey()) + { + throw new InvalidMessageException("Incomplete message."); + } + + this.serialized = serialized; + this.senderEphemeral = Curve.decodePoint(whisperMessage.getEphemeralKey().toByteArray(), 0); + this.counter = whisperMessage.getCounter(); + this.previousCounter = whisperMessage.getPreviousCounter(); + this.ciphertext = whisperMessage.getCiphertext().toByteArray(); + } catch (InvalidProtocolBufferException e) { + throw new InvalidMessageException(e); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + + public WhisperMessageV2(SecretKeySpec macKey, ECPublicKey senderEphemeral, + int counter, int previousCounter, byte[] ciphertext) + { + byte[] version = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)}; + byte[] message = WhisperMessage.newBuilder() + .setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize())) + .setCounter(counter) + .setPreviousCounter(previousCounter) + .setCiphertext(ByteString.copyFrom(ciphertext)) + .build().toByteArray(); + byte[] mac = getMac(macKey, Util.combine(version, message)); + + this.serialized = Util.combine(version, message, mac); + this.senderEphemeral = senderEphemeral; + this.counter = counter; + this.previousCounter = previousCounter; + this.ciphertext = ciphertext; + } + + public ECPublicKey getSenderEphemeral() { + return senderEphemeral; + } + + public int getCounter() { + return counter; + } + + public byte[] getBody() { + return ciphertext; + } + + public void verifyMac(SecretKeySpec macKey) + throws InvalidMessageException + { + byte[][] parts = Util.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH); + byte[] ourMac = getMac(macKey, parts[0]); + byte[] theirMac = parts[1]; + + if (!Arrays.equals(ourMac, theirMac)) { + throw new InvalidMessageException("Bad Mac!"); + } + } + + private byte[] getMac(SecretKeySpec macKey, byte[] serialized) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(macKey); + + byte[] fullMac = mac.doFinal(serialized); + return Util.trim(fullMac, MAC_LENGTH); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (java.security.InvalidKeyException e) { + throw new AssertionError(e); + } + } + + @Override + public byte[] serialize() { + return serialized; + } + + @Override + public int getType() { + return CiphertextMessage.CURRENT_WHISPER_TYPE; + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java new file mode 100644 index 0000000000..ba1dfddbb2 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/WhisperProtos.java @@ -0,0 +1,1636 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: WhisperTextProtocol.proto + +package org.whispersystems.textsecure.crypto.protocol; + +public final class WhisperProtos { + private WhisperProtos() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + } + public interface WhisperMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional bytes ephemeralKey = 1; + boolean hasEphemeralKey(); + com.google.protobuf.ByteString getEphemeralKey(); + + // optional uint32 counter = 2; + boolean hasCounter(); + int getCounter(); + + // optional uint32 previousCounter = 3; + boolean hasPreviousCounter(); + int getPreviousCounter(); + + // optional bytes ciphertext = 4; + boolean hasCiphertext(); + com.google.protobuf.ByteString getCiphertext(); + } + public static final class WhisperMessage extends + com.google.protobuf.GeneratedMessage + implements WhisperMessageOrBuilder { + // Use WhisperMessage.newBuilder() to construct. + private WhisperMessage(Builder builder) { + super(builder); + } + private WhisperMessage(boolean noInit) {} + + private static final WhisperMessage defaultInstance; + public static WhisperMessage getDefaultInstance() { + return defaultInstance; + } + + public WhisperMessage getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_WhisperMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_WhisperMessage_fieldAccessorTable; + } + + private int bitField0_; + // optional bytes ephemeralKey = 1; + public static final int EPHEMERALKEY_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString ephemeralKey_; + public boolean hasEphemeralKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getEphemeralKey() { + return ephemeralKey_; + } + + // optional uint32 counter = 2; + public static final int COUNTER_FIELD_NUMBER = 2; + private int counter_; + public boolean hasCounter() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public int getCounter() { + return counter_; + } + + // optional uint32 previousCounter = 3; + public static final int PREVIOUSCOUNTER_FIELD_NUMBER = 3; + private int previousCounter_; + public boolean hasPreviousCounter() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getPreviousCounter() { + return previousCounter_; + } + + // optional bytes ciphertext = 4; + public static final int CIPHERTEXT_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString ciphertext_; + public boolean hasCiphertext() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getCiphertext() { + return ciphertext_; + } + + private void initFields() { + ephemeralKey_ = com.google.protobuf.ByteString.EMPTY; + counter_ = 0; + previousCounter_ = 0; + ciphertext_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, ephemeralKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt32(2, counter_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(3, previousCounter_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, ciphertext_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, ephemeralKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(2, counter_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(3, previousCounter_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, ciphertext_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_WhisperMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_WhisperMessage_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + ephemeralKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + counter_ = 0; + bitField0_ = (bitField0_ & ~0x00000002); + previousCounter_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); + ciphertext_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage.getDescriptor(); + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage getDefaultInstanceForType() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage.getDefaultInstance(); + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage build() { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage buildPartial() { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage result = new org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.ephemeralKey_ = ephemeralKey_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.counter_ = counter_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.previousCounter_ = previousCounter_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.ciphertext_ = ciphertext_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage) { + return mergeFrom((org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage other) { + if (other == org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage.getDefaultInstance()) return this; + if (other.hasEphemeralKey()) { + setEphemeralKey(other.getEphemeralKey()); + } + if (other.hasCounter()) { + setCounter(other.getCounter()); + } + if (other.hasPreviousCounter()) { + setPreviousCounter(other.getPreviousCounter()); + } + if (other.hasCiphertext()) { + setCiphertext(other.getCiphertext()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + ephemeralKey_ = input.readBytes(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + counter_ = input.readUInt32(); + break; + } + case 24: { + bitField0_ |= 0x00000004; + previousCounter_ = input.readUInt32(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + ciphertext_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional bytes ephemeralKey = 1; + private com.google.protobuf.ByteString ephemeralKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasEphemeralKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getEphemeralKey() { + return ephemeralKey_; + } + public Builder setEphemeralKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + ephemeralKey_ = value; + onChanged(); + return this; + } + public Builder clearEphemeralKey() { + bitField0_ = (bitField0_ & ~0x00000001); + ephemeralKey_ = getDefaultInstance().getEphemeralKey(); + onChanged(); + return this; + } + + // optional uint32 counter = 2; + private int counter_ ; + public boolean hasCounter() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public int getCounter() { + return counter_; + } + public Builder setCounter(int value) { + bitField0_ |= 0x00000002; + counter_ = value; + onChanged(); + return this; + } + public Builder clearCounter() { + bitField0_ = (bitField0_ & ~0x00000002); + counter_ = 0; + onChanged(); + return this; + } + + // optional uint32 previousCounter = 3; + private int previousCounter_ ; + public boolean hasPreviousCounter() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getPreviousCounter() { + return previousCounter_; + } + public Builder setPreviousCounter(int value) { + bitField0_ |= 0x00000004; + previousCounter_ = value; + onChanged(); + return this; + } + public Builder clearPreviousCounter() { + bitField0_ = (bitField0_ & ~0x00000004); + previousCounter_ = 0; + onChanged(); + return this; + } + + // optional bytes ciphertext = 4; + private com.google.protobuf.ByteString ciphertext_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasCiphertext() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getCiphertext() { + return ciphertext_; + } + public Builder setCiphertext(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + ciphertext_ = value; + onChanged(); + return this; + } + public Builder clearCiphertext() { + bitField0_ = (bitField0_ & ~0x00000008); + ciphertext_ = getDefaultInstance().getCiphertext(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.WhisperMessage) + } + + static { + defaultInstance = new WhisperMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.WhisperMessage) + } + + public interface PreKeyWhisperMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 preKeyId = 1; + boolean hasPreKeyId(); + int getPreKeyId(); + + // optional bytes baseKey = 2; + boolean hasBaseKey(); + com.google.protobuf.ByteString getBaseKey(); + + // optional bytes identityKey = 3; + boolean hasIdentityKey(); + com.google.protobuf.ByteString getIdentityKey(); + + // optional bytes message = 4; + boolean hasMessage(); + com.google.protobuf.ByteString getMessage(); + } + public static final class PreKeyWhisperMessage extends + com.google.protobuf.GeneratedMessage + implements PreKeyWhisperMessageOrBuilder { + // Use PreKeyWhisperMessage.newBuilder() to construct. + private PreKeyWhisperMessage(Builder builder) { + super(builder); + } + private PreKeyWhisperMessage(boolean noInit) {} + + private static final PreKeyWhisperMessage defaultInstance; + public static PreKeyWhisperMessage getDefaultInstance() { + return defaultInstance; + } + + public PreKeyWhisperMessage getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_PreKeyWhisperMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_PreKeyWhisperMessage_fieldAccessorTable; + } + + private int bitField0_; + // optional uint32 preKeyId = 1; + public static final int PREKEYID_FIELD_NUMBER = 1; + private int preKeyId_; + public boolean hasPreKeyId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getPreKeyId() { + return preKeyId_; + } + + // optional bytes baseKey = 2; + public static final int BASEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString baseKey_; + public boolean hasBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getBaseKey() { + return baseKey_; + } + + // optional bytes identityKey = 3; + public static final int IDENTITYKEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString identityKey_; + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + + // optional bytes message = 4; + public static final int MESSAGE_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString message_; + public boolean hasMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getMessage() { + return message_; + } + + private void initFields() { + preKeyId_ = 0; + baseKey_ = com.google.protobuf.ByteString.EMPTY; + identityKey_ = com.google.protobuf.ByteString.EMPTY; + message_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, preKeyId_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, baseKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, identityKey_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, message_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, preKeyId_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, baseKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, identityKey_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, message_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_PreKeyWhisperMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_PreKeyWhisperMessage_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + preKeyId_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + baseKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + identityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + message_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.getDescriptor(); + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage getDefaultInstanceForType() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.getDefaultInstance(); + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage build() { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage buildPartial() { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage result = new org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.preKeyId_ = preKeyId_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.baseKey_ = baseKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.identityKey_ = identityKey_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.message_ = message_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage) { + return mergeFrom((org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage other) { + if (other == org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.getDefaultInstance()) return this; + if (other.hasPreKeyId()) { + setPreKeyId(other.getPreKeyId()); + } + if (other.hasBaseKey()) { + setBaseKey(other.getBaseKey()); + } + if (other.hasIdentityKey()) { + setIdentityKey(other.getIdentityKey()); + } + if (other.hasMessage()) { + setMessage(other.getMessage()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + preKeyId_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + baseKey_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + identityKey_ = input.readBytes(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + message_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 preKeyId = 1; + private int preKeyId_ ; + public boolean hasPreKeyId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getPreKeyId() { + return preKeyId_; + } + public Builder setPreKeyId(int value) { + bitField0_ |= 0x00000001; + preKeyId_ = value; + onChanged(); + return this; + } + public Builder clearPreKeyId() { + bitField0_ = (bitField0_ & ~0x00000001); + preKeyId_ = 0; + onChanged(); + return this; + } + + // optional bytes baseKey = 2; + private com.google.protobuf.ByteString baseKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getBaseKey() { + return baseKey_; + } + public Builder setBaseKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + baseKey_ = value; + onChanged(); + return this; + } + public Builder clearBaseKey() { + bitField0_ = (bitField0_ & ~0x00000002); + baseKey_ = getDefaultInstance().getBaseKey(); + onChanged(); + return this; + } + + // optional bytes identityKey = 3; + private com.google.protobuf.ByteString identityKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + public Builder setIdentityKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + identityKey_ = value; + onChanged(); + return this; + } + public Builder clearIdentityKey() { + bitField0_ = (bitField0_ & ~0x00000004); + identityKey_ = getDefaultInstance().getIdentityKey(); + onChanged(); + return this; + } + + // optional bytes message = 4; + private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getMessage() { + return message_; + } + public Builder setMessage(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + message_ = value; + onChanged(); + return this; + } + public Builder clearMessage() { + bitField0_ = (bitField0_ & ~0x00000008); + message_ = getDefaultInstance().getMessage(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.PreKeyWhisperMessage) + } + + static { + defaultInstance = new PreKeyWhisperMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.PreKeyWhisperMessage) + } + + public interface KeyExchangeMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 id = 1; + boolean hasId(); + int getId(); + + // optional bytes baseKey = 2; + boolean hasBaseKey(); + com.google.protobuf.ByteString getBaseKey(); + + // optional bytes ephemeralKey = 3; + boolean hasEphemeralKey(); + com.google.protobuf.ByteString getEphemeralKey(); + + // optional bytes identityKey = 4; + boolean hasIdentityKey(); + com.google.protobuf.ByteString getIdentityKey(); + } + public static final class KeyExchangeMessage extends + com.google.protobuf.GeneratedMessage + implements KeyExchangeMessageOrBuilder { + // Use KeyExchangeMessage.newBuilder() to construct. + private KeyExchangeMessage(Builder builder) { + super(builder); + } + private KeyExchangeMessage(boolean noInit) {} + + private static final KeyExchangeMessage defaultInstance; + public static KeyExchangeMessage getDefaultInstance() { + return defaultInstance; + } + + public KeyExchangeMessage getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_KeyExchangeMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_KeyExchangeMessage_fieldAccessorTable; + } + + private int bitField0_; + // optional uint32 id = 1; + public static final int ID_FIELD_NUMBER = 1; + private int id_; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getId() { + return id_; + } + + // optional bytes baseKey = 2; + public static final int BASEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString baseKey_; + public boolean hasBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getBaseKey() { + return baseKey_; + } + + // optional bytes ephemeralKey = 3; + public static final int EPHEMERALKEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString ephemeralKey_; + public boolean hasEphemeralKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getEphemeralKey() { + return ephemeralKey_; + } + + // optional bytes identityKey = 4; + public static final int IDENTITYKEY_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString identityKey_; + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + + private void initFields() { + id_ = 0; + baseKey_ = com.google.protobuf.ByteString.EMPTY; + ephemeralKey_ = com.google.protobuf.ByteString.EMPTY; + identityKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, baseKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, ephemeralKey_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, identityKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, baseKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, ephemeralKey_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, identityKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_KeyExchangeMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.internal_static_textsecure_KeyExchangeMessage_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + id_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + baseKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + ephemeralKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + identityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage.getDescriptor(); + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage getDefaultInstanceForType() { + return org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage.getDefaultInstance(); + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage build() { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage buildPartial() { + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage result = new org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.id_ = id_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.baseKey_ = baseKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.ephemeralKey_ = ephemeralKey_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.identityKey_ = identityKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage) { + return mergeFrom((org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage other) { + if (other == org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasBaseKey()) { + setBaseKey(other.getBaseKey()); + } + if (other.hasEphemeralKey()) { + setEphemeralKey(other.getEphemeralKey()); + } + if (other.hasIdentityKey()) { + setIdentityKey(other.getIdentityKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + id_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + baseKey_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + ephemeralKey_ = input.readBytes(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + identityKey_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 id = 1; + private int id_ ; + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getId() { + return id_; + } + public Builder setId(int value) { + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0; + onChanged(); + return this; + } + + // optional bytes baseKey = 2; + private com.google.protobuf.ByteString baseKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getBaseKey() { + return baseKey_; + } + public Builder setBaseKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + baseKey_ = value; + onChanged(); + return this; + } + public Builder clearBaseKey() { + bitField0_ = (bitField0_ & ~0x00000002); + baseKey_ = getDefaultInstance().getBaseKey(); + onChanged(); + return this; + } + + // optional bytes ephemeralKey = 3; + private com.google.protobuf.ByteString ephemeralKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasEphemeralKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getEphemeralKey() { + return ephemeralKey_; + } + public Builder setEphemeralKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + ephemeralKey_ = value; + onChanged(); + return this; + } + public Builder clearEphemeralKey() { + bitField0_ = (bitField0_ & ~0x00000004); + ephemeralKey_ = getDefaultInstance().getEphemeralKey(); + onChanged(); + return this; + } + + // optional bytes identityKey = 4; + private com.google.protobuf.ByteString identityKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasIdentityKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getIdentityKey() { + return identityKey_; + } + public Builder setIdentityKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + identityKey_ = value; + onChanged(); + return this; + } + public Builder clearIdentityKey() { + bitField0_ = (bitField0_ & ~0x00000008); + identityKey_ = getDefaultInstance().getIdentityKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.KeyExchangeMessage) + } + + static { + defaultInstance = new KeyExchangeMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.KeyExchangeMessage) + } + + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_WhisperMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_WhisperMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_PreKeyWhisperMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_PreKeyWhisperMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_KeyExchangeMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_KeyExchangeMessage_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\031WhisperTextProtocol.proto\022\ntextsecure\"" + + "d\n\016WhisperMessage\022\024\n\014ephemeralKey\030\001 \001(\014\022" + + "\017\n\007counter\030\002 \001(\r\022\027\n\017previousCounter\030\003 \001(" + + "\r\022\022\n\nciphertext\030\004 \001(\014\"_\n\024PreKeyWhisperMe" + + "ssage\022\020\n\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014" + + "\022\023\n\013identityKey\030\003 \001(\014\022\017\n\007message\030\004 \001(\014\"\\" + + "\n\022KeyExchangeMessage\022\n\n\002id\030\001 \001(\r\022\017\n\007base" + + "Key\030\002 \001(\014\022\024\n\014ephemeralKey\030\003 \001(\014\022\023\n\013ident" + + "ityKey\030\004 \001(\014B>\n-org.whispersystems.texts" + + "ecure.crypto.protocolB\rWhisperProtos" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + internal_static_textsecure_WhisperMessage_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_textsecure_WhisperMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_WhisperMessage_descriptor, + new java.lang.String[] { "EphemeralKey", "Counter", "PreviousCounter", "Ciphertext", }, + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage.class, + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage.Builder.class); + internal_static_textsecure_PreKeyWhisperMessage_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_textsecure_PreKeyWhisperMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_PreKeyWhisperMessage_descriptor, + new java.lang.String[] { "PreKeyId", "BaseKey", "IdentityKey", "Message", }, + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.class, + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.PreKeyWhisperMessage.Builder.class); + internal_static_textsecure_KeyExchangeMessage_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_textsecure_KeyExchangeMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_KeyExchangeMessage_descriptor, + new java.lang.String[] { "Id", "BaseKey", "EphemeralKey", "IdentityKey", }, + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage.class, + org.whispersystems.textsecure.crypto.protocol.WhisperProtos.KeyExchangeMessage.Builder.class); + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ratchet/ChainKey.java b/library/src/org/whispersystems/textsecure/crypto/ratchet/ChainKey.java new file mode 100644 index 0000000000..0b17a30e74 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ratchet/ChainKey.java @@ -0,0 +1,58 @@ +package org.whispersystems.textsecure.crypto.ratchet; + +import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; +import org.whispersystems.textsecure.crypto.kdf.HKDF; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class ChainKey { + + private static final byte[] MESSAGE_KEY_SEED = {0x01}; + private static final byte[] CHAIN_KEY_SEED = {0x02}; + + private final byte[] key; + private final int index; + + public ChainKey(byte[] key, int index) { + this.key = key; + this.index = index; + } + + public byte[] getKey() { + return key; + } + + public int getIndex() { + return index; + } + + public ChainKey getNextChainKey() { + byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED); + return new ChainKey(nextKey, index + 1); + } + + public MessageKeys getMessageKeys() { + HKDF kdf = new HKDF(); + byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED); + DerivedSecrets keyMaterial = kdf.deriveSecrets(inputKeyMaterial, null); + + return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index); + } + + private byte[] getBaseMaterial(byte[] seed) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + + return mac.doFinal(seed); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ratchet/MessageKeys.java b/library/src/org/whispersystems/textsecure/crypto/ratchet/MessageKeys.java new file mode 100644 index 0000000000..1ab6208de1 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ratchet/MessageKeys.java @@ -0,0 +1,28 @@ +package org.whispersystems.textsecure.crypto.ratchet; + +import javax.crypto.spec.SecretKeySpec; + +public class MessageKeys { + + private final SecretKeySpec cipherKey; + private final SecretKeySpec macKey; + private final int counter; + + public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) { + this.cipherKey = cipherKey; + this.macKey = macKey; + this.counter = counter; + } + + public SecretKeySpec getCipherKey() { + return cipherKey; + } + + public SecretKeySpec getMacKey() { + return macKey; + } + + public int getCounter() { + return counter; + } +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java b/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java new file mode 100644 index 0000000000..456416bc58 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ratchet/RatchetingSession.java @@ -0,0 +1,120 @@ +package org.whispersystems.textsecure.crypto.ratchet; + +import android.util.Pair; + +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.IdentityKeyPair; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; +import org.whispersystems.textsecure.crypto.kdf.HKDF; +import org.whispersystems.textsecure.storage.SessionRecordV2; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class RatchetingSession { + + public static void initializeSession(SessionRecordV2 sessionRecord, + ECKeyPair ourBaseKey, + ECPublicKey theirBaseKey, + ECKeyPair ourEphemeralKey, + ECPublicKey theirEphemeralKey, + IdentityKeyPair ourIdentityKey, + IdentityKey theirIdentityKey) + throws InvalidKeyException + { + if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) { + initializeSessionAsAlice(sessionRecord, ourBaseKey, theirBaseKey, theirEphemeralKey, + ourIdentityKey, theirIdentityKey); + } else { + initializeSessionAsBob(sessionRecord, ourBaseKey, theirBaseKey, + ourEphemeralKey, ourIdentityKey, theirIdentityKey); + } + } + + private static void initializeSessionAsAlice(SessionRecordV2 sessionRecord, + ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, + ECPublicKey theirEphemeralKey, + IdentityKeyPair ourIdentityKey, + IdentityKey theirIdentityKey) + throws InvalidKeyException + { + sessionRecord.setRemoteIdentityKey(theirIdentityKey); + sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey()); + + ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType()); + Pair receivingChain = calculate3DHE(ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey); + Pair sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey); + + sessionRecord.addReceiverChain(theirEphemeralKey, receivingChain.second); + sessionRecord.setSenderChain(sendingKey, sendingChain.second); + sessionRecord.setRootKey(sendingChain.first); + } + + private static void initializeSessionAsBob(SessionRecordV2 sessionRecord, + ECKeyPair ourBaseKey, ECPublicKey theirBaseKey, + ECKeyPair ourEphemeralKey, + IdentityKeyPair ourIdentityKey, + IdentityKey theirIdentityKey) + throws InvalidKeyException + { + sessionRecord.setRemoteIdentityKey(theirIdentityKey); + sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey()); + + Pair sendingChain = calculate3DHE(ourBaseKey, theirBaseKey, + ourIdentityKey, theirIdentityKey); + + sessionRecord.setSenderChain(ourEphemeralKey, sendingChain.second); + sessionRecord.setRootKey(sendingChain.first); + } + + public static Pair calculate3DHE(ECKeyPair ourEphemeral, ECPublicKey theirEphemeral, + IdentityKeyPair ourIdentity, IdentityKey theirIdentity) + throws InvalidKeyException + { + try { + ByteArrayOutputStream secrets = new ByteArrayOutputStream(); + + if (isLowEnd(ourEphemeral.getPublicKey(), theirEphemeral)) { + secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey())); + } else { + secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey())); + secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey())); + } + + secrets.write(Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey())); + + DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(secrets.toByteArray(), + "WhisperText".getBytes()); + + return new Pair(new RootKey(derivedSecrets.getCipherKey().getEncoded()), + new ChainKey(derivedSecrets.getMacKey().getEncoded(), 0)); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private static boolean isAlice(ECPublicKey ourBaseKey, ECPublicKey theirBaseKey, + ECPublicKey ourEphemeralKey, ECPublicKey theirEphemeralKey) + { + if (ourEphemeralKey.equals(ourBaseKey)) { + return false; + } + + if (theirEphemeralKey.equals(theirBaseKey)) { + return true; + } + + return isLowEnd(ourBaseKey, theirBaseKey); + } + + private static boolean isLowEnd(ECPublicKey ourKey, ECPublicKey theirKey) { + return ourKey.compareTo(theirKey) < 0; + } + + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/ratchet/RootKey.java b/library/src/org/whispersystems/textsecure/crypto/ratchet/RootKey.java new file mode 100644 index 0000000000..2d8a4d521d --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/ratchet/RootKey.java @@ -0,0 +1,38 @@ +package org.whispersystems.textsecure.crypto.ratchet; + +import android.util.Pair; + +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets; +import org.whispersystems.textsecure.crypto.kdf.HKDF; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class RootKey { + + private final byte[] key; + + public RootKey(byte[] key) { + this.key = key; + } + + public byte[] getKeyBytes() { + return key; + } + + public Pair createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral) + throws InvalidKeyException + { + HKDF kdf = new HKDF(); + byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey()); + DerivedSecrets keys = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes()); + RootKey newRootKey = new RootKey(keys.getMacKey().getEncoded()); + ChainKey newChainKey = new ChainKey(keys.getCipherKey().getEncoded(), 0); + + return new Pair(newRootKey, newChainKey); + } +} diff --git a/library/src/org/whispersystems/textsecure/storage/Record.java b/library/src/org/whispersystems/textsecure/storage/Record.java index 00634a8d75..4b69da80c8 100644 --- a/library/src/org/whispersystems/textsecure/storage/Record.java +++ b/library/src/org/whispersystems/textsecure/storage/Record.java @@ -30,8 +30,9 @@ import java.nio.channels.FileChannel; public abstract class Record { - protected static final String SESSIONS_DIRECTORY = "sessions"; - public static final String PREKEY_DIRECTORY = "prekeys"; + protected static final String SESSIONS_DIRECTORY = "sessions"; + protected static final String SESSIONS_DIRECTORY_V2 = "sessions-v2"; + public static final String PREKEY_DIRECTORY = "prekeys"; protected final String address; protected final String directory; diff --git a/library/src/org/whispersystems/textsecure/storage/Session.java b/library/src/org/whispersystems/textsecure/storage/Session.java new file mode 100644 index 0000000000..b57a68f6db --- /dev/null +++ b/library/src/org/whispersystems/textsecure/storage/Session.java @@ -0,0 +1,93 @@ +package org.whispersystems.textsecure.storage; + +import android.content.Context; +import android.util.Log; + +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.MasterSecret; + +/** + * Helper class for generating key pairs and calculating ECDH agreements. + * + * @author Moxie Marlinspike + */ + +public class Session { + + public static void clearV1SessionFor(Context context, CanonicalRecipientAddress recipient) { + //XXX Obviously we should probably do something more thorough here eventually. + LocalKeyRecord.delete(context, recipient); + RemoteKeyRecord.delete(context, recipient); + SessionRecordV1.delete(context, recipient); + } + + public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) { + Log.w("Session", "Aborting session, deleting keys..."); + clearV1SessionFor(context, recipient); + SessionRecordV2.delete(context, recipient); + } + + public static boolean hasSession(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + Log.w("Session", "Checking session..."); + return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient); + } + + public static boolean hasRemoteIdentityKey(Context context, + MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + return (hasV2Session(context, masterSecret, recipient) || + (hasV1Session(context, recipient) && + new SessionRecordV1(context, masterSecret, recipient).getIdentityKey() != null)); + } + + private static boolean hasV2Session(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + return SessionRecordV2.hasSession(context, masterSecret, recipient); + } + + private static boolean hasV1Session(Context context, CanonicalRecipientAddress recipient) { + return SessionRecordV1.hasSession(context, recipient) && + RemoteKeyRecord.hasRecord(context, recipient) && + LocalKeyRecord.hasRecord(context, recipient); + } + + public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + if (SessionRecordV2.hasSession(context, masterSecret, recipient)) { + return new SessionRecordV2(context, masterSecret, recipient).getRemoteIdentityKey(); + } else if (SessionRecordV1.hasSession(context, recipient)) { + return new SessionRecordV1(context, masterSecret, recipient).getIdentityKey(); + } else { + return null; + } + } + + public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret, + long recipientId) + { + if (SessionRecordV2.hasSession(context, masterSecret, recipientId)) { + return new SessionRecordV2(context, masterSecret, recipientId).getRemoteIdentityKey(); + } else if (SessionRecordV1.hasSession(context, recipientId)) { + return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey(); + } else { + return null; + } + } + + public static int getSessionVersion(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + if (SessionRecordV2.hasSession(context, masterSecret, recipient)) { + return new SessionRecordV2(context, masterSecret, recipient).getSessionVersion(); + } else if (SessionRecordV1.hasSession(context, recipient)) { + return new SessionRecordV1(context, masterSecret, recipient).getSessionVersion(); + } + + return 0; + } +} diff --git a/library/src/org/whispersystems/textsecure/storage/SessionKey.java b/library/src/org/whispersystems/textsecure/storage/SessionKey.java index c45fb3a080..05b24e7db1 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionKey.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionKey.java @@ -19,7 +19,7 @@ package org.whispersystems.textsecure.storage; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.crypto.kdf.NKDF; import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Util; @@ -77,13 +77,13 @@ public class SessionKey { this.localKeyId = Conversions.byteArrayToMedium(decrypted, 0); this.remoteKeyId = Conversions.byteArrayToMedium(decrypted, 3); - byte[] keyBytes = new byte[SessionCipher.CIPHER_KEY_LENGTH]; + byte[] keyBytes = new byte[NKDF.LEGACY_CIPHER_KEY_LENGTH]; System.arraycopy(decrypted, 6, keyBytes, 0, keyBytes.length); - byte[] macBytes = new byte[SessionCipher.MAC_KEY_LENGTH]; + byte[] macBytes = new byte[NKDF.LEGACY_MAC_KEY_LENGTH]; System.arraycopy(decrypted, 6 + keyBytes.length, macBytes, 0, macBytes.length); - if (decrypted.length < 6 + SessionCipher.CIPHER_KEY_LENGTH + SessionCipher.MAC_KEY_LENGTH + 1) { + if (decrypted.length < 6 + NKDF.LEGACY_CIPHER_KEY_LENGTH + NKDF.LEGACY_MAC_KEY_LENGTH + 1) { throw new InvalidMessageException("No mode included"); } diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecord.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java similarity index 73% rename from library/src/org/whispersystems/textsecure/storage/SessionRecord.java rename to library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java index af3002189a..49b1dce871 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionRecord.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV1.java @@ -1,19 +1,3 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ package org.whispersystems.textsecure.storage; import android.content.Context; @@ -36,30 +20,28 @@ import java.nio.channels.FileChannel; * @author Moxie Marlinspike */ -public class SessionRecord extends Record { +public class SessionRecordV1 extends Record { - private static final int CURRENT_VERSION_MARKER = 0X55555557; - private static final int[] VALID_VERSION_MARKERS = {CURRENT_VERSION_MARKER, 0X55555556, 0X55555555}; + private static final int CURRENT_VERSION_MARKER = 0X55555556; + private static final int[] VALID_VERSION_MARKERS = {CURRENT_VERSION_MARKER, 0X55555555}; private static final Object FILE_LOCK = new Object(); private int counter; private byte[] localFingerprint; private byte[] remoteFingerprint; - private int negotiatedSessionVersion; private int currentSessionVersion; private IdentityKey identityKey; private SessionKey sessionKeyRecord; private boolean verifiedSessionKey; - private boolean prekeyBundleRequired; private final MasterSecret masterSecret; - public SessionRecord(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) { + public SessionRecordV1(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) { this(context, masterSecret, getRecipientId(context, recipient)); } - public SessionRecord(Context context, MasterSecret masterSecret, long recipientId) { + public SessionRecordV1(Context context, MasterSecret masterSecret, long recipientId) { super(context, SESSIONS_DIRECTORY, recipientId+""); this.masterSecret = masterSecret; this.currentSessionVersion = 31337; @@ -71,8 +53,12 @@ public class SessionRecord extends Record { } public static boolean hasSession(Context context, CanonicalRecipientAddress recipient) { - Log.w("LocalKeyRecord", "Checking: " + getRecipientId(context, recipient)); - return hasRecord(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient)+""); + return hasSession(context, getRecipientId(context, recipient)); + } + + public static boolean hasSession(Context context, long recipientId) { + Log.w("SessionRecordV1", "Checking: " + recipientId); + return hasRecord(context, SESSIONS_DIRECTORY, recipientId+""); } private static long getRecipientId(Context context, CanonicalRecipientAddress recipient) { @@ -96,14 +82,6 @@ public class SessionRecord extends Record { return (currentSessionVersion == 31337 ? 0 : currentSessionVersion); } - public int getNegotiatedSessionVersion() { - return negotiatedSessionVersion; - } - - public void setNegotiatedSessionVersion(int sessionVersion) { - this.negotiatedSessionVersion = sessionVersion; - } - public void setSessionVersion(int sessionVersion) { this.currentSessionVersion = sessionVersion; } @@ -128,18 +106,6 @@ public class SessionRecord extends Record { return this.identityKey; } - public boolean isPrekeyBundleRequired() { - return prekeyBundleRequired; - } - - public void setPrekeyBundleRequired(boolean prekeyBundleRequired) { - this.prekeyBundleRequired = prekeyBundleRequired; - } - -// public void setVerifiedSessionKey(boolean verifiedSessionKey) { -// this.verifiedSessionKey = verifiedSessionKey; -// } - public boolean isVerifiedSession() { return this.verifiedSessionKey; } @@ -182,8 +148,6 @@ public class SessionRecord extends Record { writeInteger(currentSessionVersion, out); writeIdentityKey(out); writeInteger(verifiedSessionKey ? 1 : 0, out); - writeInteger(prekeyBundleRequired ? 1 : 0, out); - writeInteger(negotiatedSessionVersion, out); if (sessionKeyRecord != null) writeBlob(sessionKeyRecord.serialize(), out); @@ -230,13 +194,6 @@ public class SessionRecord extends Record { this.verifiedSessionKey = (readInteger(in) == 1); } - if (versionMarker >= 0X55555557) { - this.prekeyBundleRequired = (readInteger(in) == 1); - this.negotiatedSessionVersion = readInteger(in); - } else { - this.negotiatedSessionVersion = currentSessionVersion; - } - if (in.available() != 0) { try { this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret); @@ -265,7 +222,7 @@ public class SessionRecord extends Record { (this.sessionKeyRecord.getRemoteKeyId() == remoteKeyId) && (this.sessionKeyRecord.getMode() == mode)) { - return this.sessionKeyRecord; + return this.sessionKeyRecord; } return null; diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java new file mode 100644 index 0000000000..6d9bc96595 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/storage/SessionRecordV2.java @@ -0,0 +1,505 @@ +/** + * Copyright (C) 2013 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecure.storage; + +import android.content.Context; +import android.util.Log; +import android.util.Pair; + +import com.google.protobuf.ByteString; + +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.IdentityKeyPair; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.ratchet.ChainKey; +import org.whispersystems.textsecure.crypto.ratchet.MessageKeys; +import org.whispersystems.textsecure.crypto.ratchet.RootKey; +import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain; +import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange; +import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.Iterator; +import java.util.List; + +import javax.crypto.spec.SecretKeySpec; + +/** + * A disk record representing a current session. + * + * @author Moxie Marlinspike + */ + +public class SessionRecordV2 extends Record { + + private static final Object FILE_LOCK = new Object(); + private static final int CURRENT_VERSION = 1; + + private final MasterSecret masterSecret; + private StorageProtos.SessionStructure sessionStructure = + StorageProtos.SessionStructure.newBuilder().build(); + + public SessionRecordV2(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) { + this(context, masterSecret, getRecipientId(context, recipient)); + } + + public SessionRecordV2(Context context, MasterSecret masterSecret, long recipientId) { + super(context, SESSIONS_DIRECTORY_V2, recipientId+""); + this.masterSecret = masterSecret; + loadData(); + } + + public static void delete(Context context, CanonicalRecipientAddress recipient) { + delete(context, SESSIONS_DIRECTORY_V2, getRecipientId(context, recipient) + ""); + } + + public static boolean hasSession(Context context, MasterSecret masterSecret, + CanonicalRecipientAddress recipient) + { + return hasSession(context, masterSecret, getRecipientId(context, recipient)); + } + + public static boolean hasSession(Context context, MasterSecret masterSecret, long recipientId) { + return hasRecord(context, SESSIONS_DIRECTORY_V2, recipientId+"") && + new SessionRecordV2(context, masterSecret, recipientId).hasSenderChain(); + } + + private static long getRecipientId(Context context, CanonicalRecipientAddress recipient) { + return recipient.getCanonicalAddress(context); + } + + public void clear() { + this.sessionStructure = StorageProtos.SessionStructure.newBuilder().build(); + } + + public void setSessionVersion(int version) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setSessionVersion(version) + .build(); + } + + public int getSessionVersion() { + return this.sessionStructure.getSessionVersion(); + } + + public void setRemoteIdentityKey(IdentityKey identityKey) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize())) + .build(); + } + + public void setLocalIdentityKey(IdentityKey identityKey) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize())) + .build(); + } + + public IdentityKey getRemoteIdentityKey() { + try { + if (!this.sessionStructure.hasRemoteIdentityPublic()) { + return null; + } + + return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0); + } catch (InvalidKeyException e) { + Log.w("SessionRecordV2", e); + return null; + } + } + + public IdentityKey getLocalIdentityKey() { + try { + return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public int getPreviousCounter() { + return sessionStructure.getPreviousCounter(); + } + + public void setPreviousCounter(int previousCounter) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setPreviousCounter(previousCounter) + .build(); + } + + public RootKey getRootKey() { + return new RootKey(this.sessionStructure.getRootKey().toByteArray()); + } + + public void setRootKey(RootKey rootKey) { + this.sessionStructure = this.sessionStructure.toBuilder() + .setRootKey(ByteString.copyFrom(rootKey.getKeyBytes())) + .build(); + } + + public ECPublicKey getSenderEphemeral() { + try { + return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public ECKeyPair getSenderEphemeralPair() { + ECPublicKey publicKey = getSenderEphemeral(); + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), + sessionStructure.getSenderChain() + .getSenderEphemeralPrivate() + .toByteArray()); + + return new ECKeyPair(publicKey, privateKey); + } + + public boolean hasReceiverChain(ECPublicKey senderEphemeral) { + return getReceiverChain(senderEphemeral) != null; + } + + public boolean hasSenderChain() { + return sessionStructure.hasSenderChain(); + } + + private Pair getReceiverChain(ECPublicKey senderEphemeral) { + List receiverChains = sessionStructure.getReceiverChainsList(); + int index = 0; + + for (Chain receiverChain : receiverChains) { + try { + ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0); + + if (chainSenderEphemeral.equals(senderEphemeral)) { + return new Pair(receiverChain,index); + } + } catch (InvalidKeyException e) { + Log.w("SessionRecordV2", e); + } + + index++; + } + + return null; + } + + public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) { + Pair receiverChainAndIndex = getReceiverChain(senderEphemeral); + Chain receiverChain = receiverChainAndIndex.first; + + if (receiverChain == null) { + return null; + } else { + return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(), + receiverChain.getChainKey().getIndex()); + } + } + + public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) { + Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(chainKey.getKey())) + .setIndex(chainKey.getIndex()) + .build(); + + Chain chain = Chain.newBuilder() + .setChainKey(chainKeyStructure) + .setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize())) + .build(); + + // XXX knock old chain out. + this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build(); + } + + public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) { + Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(chainKey.getKey())) + .setIndex(chainKey.getIndex()) + .build(); + + Chain senderChain = Chain.newBuilder() + .setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize())) + .setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize())) + .setChainKey(chainKeyStructure) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build(); + } + + public ChainKey getSenderChainKey() { + Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey(); + return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex()); + } + + + public void setSenderChainKey(ChainKey nextChainKey) { + Chain.ChainKey chainKey = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(nextChainKey.getKey())) + .setIndex(nextChainKey.getIndex()) + .build(); + + Chain chain = sessionStructure.getSenderChain().toBuilder() + .setChainKey(chainKey).build(); + + this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build(); + } + + public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + + if (chain == null) { + return false; + } + + List messageKeyList = chain.getMessageKeysList(); + + for (Chain.MessageKey messageKey : messageKeyList) { + if (messageKey.getIndex() == counter) { + return true; + } + } + + return false; + } + + public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + + if (chain == null) { + return null; + } + + List messageKeyList = chain.getMessageKeysList(); + Iterator messageKeyIterator = messageKeyList.iterator(); + MessageKeys result = null; + + while (messageKeyIterator.hasNext()) { + Chain.MessageKey messageKey = messageKeyIterator.next(); + + if (messageKey.getIndex() == counter) { + result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"), + new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"), + messageKey.getIndex()); + + messageKeyIterator.remove(); + break; + } + } + + Chain updatedChain = chain.toBuilder().clearMessageKeys() + .addAllMessageKeys(messageKeyList) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setReceiverChains(chainAndIndex.second, updatedChain) + .build(); + + return result; + } + + public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder() + .setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded())) + .setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded())) + .setIndex(messageKeys.getCounter()) + .build(); + + Chain updatedChain = chain.toBuilder() + .addMessageKeys(messageKeyStructure) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setReceiverChains(chainAndIndex.second, updatedChain) + .build(); + } + + public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) { + Pair chainAndIndex = getReceiverChain(senderEphemeral); + Chain chain = chainAndIndex.first; + + Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder() + .setKey(ByteString.copyFrom(chainKey.getKey())) + .setIndex(chainKey.getIndex()) + .build(); + + Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setReceiverChains(chainAndIndex.second, updatedChain) + .build(); + } + + public void setPendingKeyExchange(int sequence, + ECKeyPair ourBaseKey, + ECKeyPair ourEphemeralKey, + IdentityKeyPair ourIdentityKey) + { + PendingKeyExchange structure = + PendingKeyExchange.newBuilder() + .setSequence(sequence) + .setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize())) + .setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize())) + .setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize())) + .setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize())) + .setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize())) + .setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize())) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setPendingKeyExchange(structure) + .build(); + } + + public int getPendingKeyExchangeSequence() { + return sessionStructure.getPendingKeyExchange().getSequence(); + } + + public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException { + ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() + .getLocalBaseKey().toByteArray(), 0); + + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), + sessionStructure.getPendingKeyExchange() + .getLocalBaseKeyPrivate() + .toByteArray()); + + return new ECKeyPair(publicKey, privateKey); + } + + public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException { + ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange() + .getLocalEphemeralKey().toByteArray(), 0); + + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(), + sessionStructure.getPendingKeyExchange() + .getLocalEphemeralKeyPrivate() + .toByteArray()); + + return new ECKeyPair(publicKey, privateKey); + } + + public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException { + IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange() + .getLocalIdentityKey().toByteArray(), 0); + + ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(), + sessionStructure.getPendingKeyExchange() + .getLocalIdentityKeyPrivate() + .toByteArray()); + + return new IdentityKeyPair(publicKey, privateKey); + } + + public boolean hasPendingKeyExchange() { + return sessionStructure.hasPendingKeyExchange(); + } + + public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) { + PendingPreKey pending = PendingPreKey.newBuilder() + .setPreKeyId(preKeyId) + .setBaseKey(ByteString.copyFrom(baseKey.serialize())) + .build(); + + this.sessionStructure = this.sessionStructure.toBuilder() + .setPendingPreKey(pending) + .build(); + } + + public boolean hasPendingPreKey() { + return this.sessionStructure.hasPendingPreKey(); + } + + public Pair getPendingPreKey() { + try { + return new Pair(sessionStructure.getPendingPreKey().getPreKeyId(), + Curve.decodePoint(sessionStructure.getPendingPreKey() + .getBaseKey() + .toByteArray(), 0)); + } catch (InvalidKeyException e) { + throw new AssertionError(e); + } + } + + public void clearPendingPreKey() { + this.sessionStructure = this.sessionStructure.toBuilder() + .clearPendingPreKey() + .build(); + } + + public void save() { + synchronized (FILE_LOCK) { + try { + RandomAccessFile file = openRandomAccessFile(); + FileChannel out = file.getChannel(); + out.position(0); + + MasterCipher cipher = new MasterCipher(masterSecret); + writeInteger(CURRENT_VERSION, out); + writeBlob(cipher.encryptBytes(sessionStructure.toByteArray()), out); + + out.truncate(out.position()); + file.close(); + } catch (IOException ioe) { + throw new IllegalArgumentException(ioe); + } + } + } + + private void loadData() { + synchronized (FILE_LOCK) { + try { + FileInputStream in = this.openInputStream(); + int versionMarker = readInteger(in); + + if (versionMarker > CURRENT_VERSION) { + throw new AssertionError("Unknown version: " + versionMarker); + } + + MasterCipher cipher = new MasterCipher(masterSecret); + byte[] encryptedBlob = readBlob(in); + + + this.sessionStructure = StorageProtos.SessionStructure + .parseFrom(cipher.decryptBytes(encryptedBlob)); + + in.close(); + } catch (FileNotFoundException e) { + Log.w("SessionRecordV2", "No session information found."); + // XXX + } catch (IOException ioe) { + Log.w("SessionRecordV2", ioe); + // XXX + } catch (InvalidMessageException e) { + Log.w("SessionRecordV2", e); + } + } + } + +} diff --git a/library/src/org/whispersystems/textsecure/storage/StorageProtos.java b/library/src/org/whispersystems/textsecure/storage/StorageProtos.java new file mode 100644 index 0000000000..7f51de31f8 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/storage/StorageProtos.java @@ -0,0 +1,4187 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: LocalStorageProtocol.proto + +package org.whispersystems.textsecure.storage; + +public final class StorageProtos { + private StorageProtos() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + } + public interface SessionStructureOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 sessionVersion = 1; + boolean hasSessionVersion(); + int getSessionVersion(); + + // optional bytes localIdentityPublic = 2; + boolean hasLocalIdentityPublic(); + com.google.protobuf.ByteString getLocalIdentityPublic(); + + // optional bytes remoteIdentityPublic = 3; + boolean hasRemoteIdentityPublic(); + com.google.protobuf.ByteString getRemoteIdentityPublic(); + + // optional bytes rootKey = 4; + boolean hasRootKey(); + com.google.protobuf.ByteString getRootKey(); + + // optional uint32 previousCounter = 5; + boolean hasPreviousCounter(); + int getPreviousCounter(); + + // optional .textsecure.SessionStructure.Chain senderChain = 6; + boolean hasSenderChain(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getSenderChain(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder getSenderChainOrBuilder(); + + // repeated .textsecure.SessionStructure.Chain receiverChains = 7; + java.util.List + getReceiverChainsList(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getReceiverChains(int index); + int getReceiverChainsCount(); + java.util.List + getReceiverChainsOrBuilderList(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder getReceiverChainsOrBuilder( + int index); + + // optional .textsecure.SessionStructure.PendingKeyExchange pendingKeyExchange = 8; + boolean hasPendingKeyExchange(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange getPendingKeyExchange(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder getPendingKeyExchangeOrBuilder(); + + // optional .textsecure.SessionStructure.PendingPreKey pendingPreKey = 9; + boolean hasPendingPreKey(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey getPendingPreKey(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder getPendingPreKeyOrBuilder(); + } + public static final class SessionStructure extends + com.google.protobuf.GeneratedMessage + implements SessionStructureOrBuilder { + // Use SessionStructure.newBuilder() to construct. + private SessionStructure(Builder builder) { + super(builder); + } + private SessionStructure(boolean noInit) {} + + private static final SessionStructure defaultInstance; + public static SessionStructure getDefaultInstance() { + return defaultInstance; + } + + public SessionStructure getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_fieldAccessorTable; + } + + public interface ChainOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional bytes senderEphemeral = 1; + boolean hasSenderEphemeral(); + com.google.protobuf.ByteString getSenderEphemeral(); + + // optional bytes senderEphemeralPrivate = 2; + boolean hasSenderEphemeralPrivate(); + com.google.protobuf.ByteString getSenderEphemeralPrivate(); + + // optional .textsecure.SessionStructure.Chain.ChainKey chainKey = 3; + boolean hasChainKey(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey getChainKey(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder getChainKeyOrBuilder(); + + // repeated .textsecure.SessionStructure.Chain.MessageKey messageKeys = 4; + java.util.List + getMessageKeysList(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey getMessageKeys(int index); + int getMessageKeysCount(); + java.util.List + getMessageKeysOrBuilderList(); + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder getMessageKeysOrBuilder( + int index); + } + public static final class Chain extends + com.google.protobuf.GeneratedMessage + implements ChainOrBuilder { + // Use Chain.newBuilder() to construct. + private Chain(Builder builder) { + super(builder); + } + private Chain(boolean noInit) {} + + private static final Chain defaultInstance; + public static Chain getDefaultInstance() { + return defaultInstance; + } + + public Chain getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_fieldAccessorTable; + } + + public interface ChainKeyOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 index = 1; + boolean hasIndex(); + int getIndex(); + + // optional bytes key = 2; + boolean hasKey(); + com.google.protobuf.ByteString getKey(); + } + public static final class ChainKey extends + com.google.protobuf.GeneratedMessage + implements ChainKeyOrBuilder { + // Use ChainKey.newBuilder() to construct. + private ChainKey(Builder builder) { + super(builder); + } + private ChainKey(boolean noInit) {} + + private static final ChainKey defaultInstance; + public static ChainKey getDefaultInstance() { + return defaultInstance; + } + + public ChainKey getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_ChainKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_ChainKey_fieldAccessorTable; + } + + private int bitField0_; + // optional uint32 index = 1; + public static final int INDEX_FIELD_NUMBER = 1; + private int index_; + public boolean hasIndex() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getIndex() { + return index_; + } + + // optional bytes key = 2; + public static final int KEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString key_; + public boolean hasKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getKey() { + return key_; + } + + private void initFields() { + index_ = 0; + key_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, index_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, key_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, index_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, key_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_ChainKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_ChainKey_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + index_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + key_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey build() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey result = new org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.index_ = index_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.key_ = key_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance()) return this; + if (other.hasIndex()) { + setIndex(other.getIndex()); + } + if (other.hasKey()) { + setKey(other.getKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + index_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + key_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 index = 1; + private int index_ ; + public boolean hasIndex() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getIndex() { + return index_; + } + public Builder setIndex(int value) { + bitField0_ |= 0x00000001; + index_ = value; + onChanged(); + return this; + } + public Builder clearIndex() { + bitField0_ = (bitField0_ & ~0x00000001); + index_ = 0; + onChanged(); + return this; + } + + // optional bytes key = 2; + private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getKey() { + return key_; + } + public Builder setKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + key_ = value; + onChanged(); + return this; + } + public Builder clearKey() { + bitField0_ = (bitField0_ & ~0x00000002); + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.Chain.ChainKey) + } + + static { + defaultInstance = new ChainKey(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SessionStructure.Chain.ChainKey) + } + + public interface MessageKeyOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 index = 1; + boolean hasIndex(); + int getIndex(); + + // optional bytes cipherKey = 2; + boolean hasCipherKey(); + com.google.protobuf.ByteString getCipherKey(); + + // optional bytes macKey = 3; + boolean hasMacKey(); + com.google.protobuf.ByteString getMacKey(); + } + public static final class MessageKey extends + com.google.protobuf.GeneratedMessage + implements MessageKeyOrBuilder { + // Use MessageKey.newBuilder() to construct. + private MessageKey(Builder builder) { + super(builder); + } + private MessageKey(boolean noInit) {} + + private static final MessageKey defaultInstance; + public static MessageKey getDefaultInstance() { + return defaultInstance; + } + + public MessageKey getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable; + } + + private int bitField0_; + // optional uint32 index = 1; + public static final int INDEX_FIELD_NUMBER = 1; + private int index_; + public boolean hasIndex() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getIndex() { + return index_; + } + + // optional bytes cipherKey = 2; + public static final int CIPHERKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString cipherKey_; + public boolean hasCipherKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getCipherKey() { + return cipherKey_; + } + + // optional bytes macKey = 3; + public static final int MACKEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString macKey_; + public boolean hasMacKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getMacKey() { + return macKey_; + } + + private void initFields() { + index_ = 0; + cipherKey_ = com.google.protobuf.ByteString.EMPTY; + macKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, index_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, cipherKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, macKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, index_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, cipherKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, macKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + index_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + cipherKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + macKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey build() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey result = new org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.index_ = index_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.cipherKey_ = cipherKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.macKey_ = macKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.getDefaultInstance()) return this; + if (other.hasIndex()) { + setIndex(other.getIndex()); + } + if (other.hasCipherKey()) { + setCipherKey(other.getCipherKey()); + } + if (other.hasMacKey()) { + setMacKey(other.getMacKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + index_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + cipherKey_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + macKey_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 index = 1; + private int index_ ; + public boolean hasIndex() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getIndex() { + return index_; + } + public Builder setIndex(int value) { + bitField0_ |= 0x00000001; + index_ = value; + onChanged(); + return this; + } + public Builder clearIndex() { + bitField0_ = (bitField0_ & ~0x00000001); + index_ = 0; + onChanged(); + return this; + } + + // optional bytes cipherKey = 2; + private com.google.protobuf.ByteString cipherKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasCipherKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getCipherKey() { + return cipherKey_; + } + public Builder setCipherKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + cipherKey_ = value; + onChanged(); + return this; + } + public Builder clearCipherKey() { + bitField0_ = (bitField0_ & ~0x00000002); + cipherKey_ = getDefaultInstance().getCipherKey(); + onChanged(); + return this; + } + + // optional bytes macKey = 3; + private com.google.protobuf.ByteString macKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasMacKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getMacKey() { + return macKey_; + } + public Builder setMacKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + macKey_ = value; + onChanged(); + return this; + } + public Builder clearMacKey() { + bitField0_ = (bitField0_ & ~0x00000004); + macKey_ = getDefaultInstance().getMacKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.Chain.MessageKey) + } + + static { + defaultInstance = new MessageKey(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SessionStructure.Chain.MessageKey) + } + + private int bitField0_; + // optional bytes senderEphemeral = 1; + public static final int SENDEREPHEMERAL_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString senderEphemeral_; + public boolean hasSenderEphemeral() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getSenderEphemeral() { + return senderEphemeral_; + } + + // optional bytes senderEphemeralPrivate = 2; + public static final int SENDEREPHEMERALPRIVATE_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString senderEphemeralPrivate_; + public boolean hasSenderEphemeralPrivate() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getSenderEphemeralPrivate() { + return senderEphemeralPrivate_; + } + + // optional .textsecure.SessionStructure.Chain.ChainKey chainKey = 3; + public static final int CHAINKEY_FIELD_NUMBER = 3; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey chainKey_; + public boolean hasChainKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey getChainKey() { + return chainKey_; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder getChainKeyOrBuilder() { + return chainKey_; + } + + // repeated .textsecure.SessionStructure.Chain.MessageKey messageKeys = 4; + public static final int MESSAGEKEYS_FIELD_NUMBER = 4; + private java.util.List messageKeys_; + public java.util.List getMessageKeysList() { + return messageKeys_; + } + public java.util.List + getMessageKeysOrBuilderList() { + return messageKeys_; + } + public int getMessageKeysCount() { + return messageKeys_.size(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey getMessageKeys(int index) { + return messageKeys_.get(index); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder getMessageKeysOrBuilder( + int index) { + return messageKeys_.get(index); + } + + private void initFields() { + senderEphemeral_ = com.google.protobuf.ByteString.EMPTY; + senderEphemeralPrivate_ = com.google.protobuf.ByteString.EMPTY; + chainKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance(); + messageKeys_ = java.util.Collections.emptyList(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, senderEphemeral_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, senderEphemeralPrivate_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeMessage(3, chainKey_); + } + for (int i = 0; i < messageKeys_.size(); i++) { + output.writeMessage(4, messageKeys_.get(i)); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, senderEphemeral_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, senderEphemeralPrivate_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, chainKey_); + } + for (int i = 0; i < messageKeys_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(4, messageKeys_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_Chain_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getChainKeyFieldBuilder(); + getMessageKeysFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + senderEphemeral_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + senderEphemeralPrivate_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + if (chainKeyBuilder_ == null) { + chainKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance(); + } else { + chainKeyBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + if (messageKeysBuilder_ == null) { + messageKeys_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008); + } else { + messageKeysBuilder_.clear(); + } + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain build() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain result = new org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.senderEphemeral_ = senderEphemeral_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.senderEphemeralPrivate_ = senderEphemeralPrivate_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + if (chainKeyBuilder_ == null) { + result.chainKey_ = chainKey_; + } else { + result.chainKey_ = chainKeyBuilder_.build(); + } + if (messageKeysBuilder_ == null) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { + messageKeys_ = java.util.Collections.unmodifiableList(messageKeys_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.messageKeys_ = messageKeys_; + } else { + result.messageKeys_ = messageKeysBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance()) return this; + if (other.hasSenderEphemeral()) { + setSenderEphemeral(other.getSenderEphemeral()); + } + if (other.hasSenderEphemeralPrivate()) { + setSenderEphemeralPrivate(other.getSenderEphemeralPrivate()); + } + if (other.hasChainKey()) { + mergeChainKey(other.getChainKey()); + } + if (messageKeysBuilder_ == null) { + if (!other.messageKeys_.isEmpty()) { + if (messageKeys_.isEmpty()) { + messageKeys_ = other.messageKeys_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureMessageKeysIsMutable(); + messageKeys_.addAll(other.messageKeys_); + } + onChanged(); + } + } else { + if (!other.messageKeys_.isEmpty()) { + if (messageKeysBuilder_.isEmpty()) { + messageKeysBuilder_.dispose(); + messageKeysBuilder_ = null; + messageKeys_ = other.messageKeys_; + bitField0_ = (bitField0_ & ~0x00000008); + messageKeysBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getMessageKeysFieldBuilder() : null; + } else { + messageKeysBuilder_.addAllMessages(other.messageKeys_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + senderEphemeral_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + senderEphemeralPrivate_ = input.readBytes(); + break; + } + case 26: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.newBuilder(); + if (hasChainKey()) { + subBuilder.mergeFrom(getChainKey()); + } + input.readMessage(subBuilder, extensionRegistry); + setChainKey(subBuilder.buildPartial()); + break; + } + case 34: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.newBuilder(); + input.readMessage(subBuilder, extensionRegistry); + addMessageKeys(subBuilder.buildPartial()); + break; + } + } + } + } + + private int bitField0_; + + // optional bytes senderEphemeral = 1; + private com.google.protobuf.ByteString senderEphemeral_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasSenderEphemeral() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getSenderEphemeral() { + return senderEphemeral_; + } + public Builder setSenderEphemeral(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + senderEphemeral_ = value; + onChanged(); + return this; + } + public Builder clearSenderEphemeral() { + bitField0_ = (bitField0_ & ~0x00000001); + senderEphemeral_ = getDefaultInstance().getSenderEphemeral(); + onChanged(); + return this; + } + + // optional bytes senderEphemeralPrivate = 2; + private com.google.protobuf.ByteString senderEphemeralPrivate_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasSenderEphemeralPrivate() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getSenderEphemeralPrivate() { + return senderEphemeralPrivate_; + } + public Builder setSenderEphemeralPrivate(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + senderEphemeralPrivate_ = value; + onChanged(); + return this; + } + public Builder clearSenderEphemeralPrivate() { + bitField0_ = (bitField0_ & ~0x00000002); + senderEphemeralPrivate_ = getDefaultInstance().getSenderEphemeralPrivate(); + onChanged(); + return this; + } + + // optional .textsecure.SessionStructure.Chain.ChainKey chainKey = 3; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey chainKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder> chainKeyBuilder_; + public boolean hasChainKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey getChainKey() { + if (chainKeyBuilder_ == null) { + return chainKey_; + } else { + return chainKeyBuilder_.getMessage(); + } + } + public Builder setChainKey(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey value) { + if (chainKeyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + chainKey_ = value; + onChanged(); + } else { + chainKeyBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder setChainKey( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder builderForValue) { + if (chainKeyBuilder_ == null) { + chainKey_ = builderForValue.build(); + onChanged(); + } else { + chainKeyBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder mergeChainKey(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey value) { + if (chainKeyBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + chainKey_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance()) { + chainKey_ = + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.newBuilder(chainKey_).mergeFrom(value).buildPartial(); + } else { + chainKey_ = value; + } + onChanged(); + } else { + chainKeyBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder clearChainKey() { + if (chainKeyBuilder_ == null) { + chainKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.getDefaultInstance(); + onChanged(); + } else { + chainKeyBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder getChainKeyBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getChainKeyFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder getChainKeyOrBuilder() { + if (chainKeyBuilder_ != null) { + return chainKeyBuilder_.getMessageOrBuilder(); + } else { + return chainKey_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder> + getChainKeyFieldBuilder() { + if (chainKeyBuilder_ == null) { + chainKeyBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKeyOrBuilder>( + chainKey_, + getParentForChildren(), + isClean()); + chainKey_ = null; + } + return chainKeyBuilder_; + } + + // repeated .textsecure.SessionStructure.Chain.MessageKey messageKeys = 4; + private java.util.List messageKeys_ = + java.util.Collections.emptyList(); + private void ensureMessageKeysIsMutable() { + if (!((bitField0_ & 0x00000008) == 0x00000008)) { + messageKeys_ = new java.util.ArrayList(messageKeys_); + bitField0_ |= 0x00000008; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder> messageKeysBuilder_; + + public java.util.List getMessageKeysList() { + if (messageKeysBuilder_ == null) { + return java.util.Collections.unmodifiableList(messageKeys_); + } else { + return messageKeysBuilder_.getMessageList(); + } + } + public int getMessageKeysCount() { + if (messageKeysBuilder_ == null) { + return messageKeys_.size(); + } else { + return messageKeysBuilder_.getCount(); + } + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey getMessageKeys(int index) { + if (messageKeysBuilder_ == null) { + return messageKeys_.get(index); + } else { + return messageKeysBuilder_.getMessage(index); + } + } + public Builder setMessageKeys( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey value) { + if (messageKeysBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMessageKeysIsMutable(); + messageKeys_.set(index, value); + onChanged(); + } else { + messageKeysBuilder_.setMessage(index, value); + } + return this; + } + public Builder setMessageKeys( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder builderForValue) { + if (messageKeysBuilder_ == null) { + ensureMessageKeysIsMutable(); + messageKeys_.set(index, builderForValue.build()); + onChanged(); + } else { + messageKeysBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + public Builder addMessageKeys(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey value) { + if (messageKeysBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMessageKeysIsMutable(); + messageKeys_.add(value); + onChanged(); + } else { + messageKeysBuilder_.addMessage(value); + } + return this; + } + public Builder addMessageKeys( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey value) { + if (messageKeysBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMessageKeysIsMutable(); + messageKeys_.add(index, value); + onChanged(); + } else { + messageKeysBuilder_.addMessage(index, value); + } + return this; + } + public Builder addMessageKeys( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder builderForValue) { + if (messageKeysBuilder_ == null) { + ensureMessageKeysIsMutable(); + messageKeys_.add(builderForValue.build()); + onChanged(); + } else { + messageKeysBuilder_.addMessage(builderForValue.build()); + } + return this; + } + public Builder addMessageKeys( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder builderForValue) { + if (messageKeysBuilder_ == null) { + ensureMessageKeysIsMutable(); + messageKeys_.add(index, builderForValue.build()); + onChanged(); + } else { + messageKeysBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + public Builder addAllMessageKeys( + java.lang.Iterable values) { + if (messageKeysBuilder_ == null) { + ensureMessageKeysIsMutable(); + super.addAll(values, messageKeys_); + onChanged(); + } else { + messageKeysBuilder_.addAllMessages(values); + } + return this; + } + public Builder clearMessageKeys() { + if (messageKeysBuilder_ == null) { + messageKeys_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + } else { + messageKeysBuilder_.clear(); + } + return this; + } + public Builder removeMessageKeys(int index) { + if (messageKeysBuilder_ == null) { + ensureMessageKeysIsMutable(); + messageKeys_.remove(index); + onChanged(); + } else { + messageKeysBuilder_.remove(index); + } + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder getMessageKeysBuilder( + int index) { + return getMessageKeysFieldBuilder().getBuilder(index); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder getMessageKeysOrBuilder( + int index) { + if (messageKeysBuilder_ == null) { + return messageKeys_.get(index); } else { + return messageKeysBuilder_.getMessageOrBuilder(index); + } + } + public java.util.List + getMessageKeysOrBuilderList() { + if (messageKeysBuilder_ != null) { + return messageKeysBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(messageKeys_); + } + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder addMessageKeysBuilder() { + return getMessageKeysFieldBuilder().addBuilder( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.getDefaultInstance()); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder addMessageKeysBuilder( + int index) { + return getMessageKeysFieldBuilder().addBuilder( + index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.getDefaultInstance()); + } + public java.util.List + getMessageKeysBuilderList() { + return getMessageKeysFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder> + getMessageKeysFieldBuilder() { + if (messageKeysBuilder_ == null) { + messageKeysBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKeyOrBuilder>( + messageKeys_, + ((bitField0_ & 0x00000008) == 0x00000008), + getParentForChildren(), + isClean()); + messageKeys_ = null; + } + return messageKeysBuilder_; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.Chain) + } + + static { + defaultInstance = new Chain(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SessionStructure.Chain) + } + + public interface PendingKeyExchangeOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 sequence = 1; + boolean hasSequence(); + int getSequence(); + + // optional bytes localBaseKey = 2; + boolean hasLocalBaseKey(); + com.google.protobuf.ByteString getLocalBaseKey(); + + // optional bytes localBaseKeyPrivate = 3; + boolean hasLocalBaseKeyPrivate(); + com.google.protobuf.ByteString getLocalBaseKeyPrivate(); + + // optional bytes localEphemeralKey = 4; + boolean hasLocalEphemeralKey(); + com.google.protobuf.ByteString getLocalEphemeralKey(); + + // optional bytes localEphemeralKeyPrivate = 5; + boolean hasLocalEphemeralKeyPrivate(); + com.google.protobuf.ByteString getLocalEphemeralKeyPrivate(); + + // optional bytes localIdentityKey = 7; + boolean hasLocalIdentityKey(); + com.google.protobuf.ByteString getLocalIdentityKey(); + + // optional bytes localIdentityKeyPrivate = 8; + boolean hasLocalIdentityKeyPrivate(); + com.google.protobuf.ByteString getLocalIdentityKeyPrivate(); + } + public static final class PendingKeyExchange extends + com.google.protobuf.GeneratedMessage + implements PendingKeyExchangeOrBuilder { + // Use PendingKeyExchange.newBuilder() to construct. + private PendingKeyExchange(Builder builder) { + super(builder); + } + private PendingKeyExchange(boolean noInit) {} + + private static final PendingKeyExchange defaultInstance; + public static PendingKeyExchange getDefaultInstance() { + return defaultInstance; + } + + public PendingKeyExchange getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingKeyExchange_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable; + } + + private int bitField0_; + // optional uint32 sequence = 1; + public static final int SEQUENCE_FIELD_NUMBER = 1; + private int sequence_; + public boolean hasSequence() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getSequence() { + return sequence_; + } + + // optional bytes localBaseKey = 2; + public static final int LOCALBASEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString localBaseKey_; + public boolean hasLocalBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getLocalBaseKey() { + return localBaseKey_; + } + + // optional bytes localBaseKeyPrivate = 3; + public static final int LOCALBASEKEYPRIVATE_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString localBaseKeyPrivate_; + public boolean hasLocalBaseKeyPrivate() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getLocalBaseKeyPrivate() { + return localBaseKeyPrivate_; + } + + // optional bytes localEphemeralKey = 4; + public static final int LOCALEPHEMERALKEY_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString localEphemeralKey_; + public boolean hasLocalEphemeralKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getLocalEphemeralKey() { + return localEphemeralKey_; + } + + // optional bytes localEphemeralKeyPrivate = 5; + public static final int LOCALEPHEMERALKEYPRIVATE_FIELD_NUMBER = 5; + private com.google.protobuf.ByteString localEphemeralKeyPrivate_; + public boolean hasLocalEphemeralKeyPrivate() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public com.google.protobuf.ByteString getLocalEphemeralKeyPrivate() { + return localEphemeralKeyPrivate_; + } + + // optional bytes localIdentityKey = 7; + public static final int LOCALIDENTITYKEY_FIELD_NUMBER = 7; + private com.google.protobuf.ByteString localIdentityKey_; + public boolean hasLocalIdentityKey() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public com.google.protobuf.ByteString getLocalIdentityKey() { + return localIdentityKey_; + } + + // optional bytes localIdentityKeyPrivate = 8; + public static final int LOCALIDENTITYKEYPRIVATE_FIELD_NUMBER = 8; + private com.google.protobuf.ByteString localIdentityKeyPrivate_; + public boolean hasLocalIdentityKeyPrivate() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + public com.google.protobuf.ByteString getLocalIdentityKeyPrivate() { + return localIdentityKeyPrivate_; + } + + private void initFields() { + sequence_ = 0; + localBaseKey_ = com.google.protobuf.ByteString.EMPTY; + localBaseKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + localEphemeralKey_ = com.google.protobuf.ByteString.EMPTY; + localEphemeralKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + localIdentityKey_ = com.google.protobuf.ByteString.EMPTY; + localIdentityKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, sequence_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, localBaseKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, localBaseKeyPrivate_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, localEphemeralKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(5, localEphemeralKeyPrivate_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBytes(7, localIdentityKey_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + output.writeBytes(8, localIdentityKeyPrivate_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, sequence_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, localBaseKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, localBaseKeyPrivate_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, localEphemeralKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(5, localEphemeralKeyPrivate_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(7, localIdentityKey_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(8, localIdentityKeyPrivate_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingKeyExchange_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + sequence_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + localBaseKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + localBaseKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + localEphemeralKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + localEphemeralKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); + localIdentityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000020); + localIdentityKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000040); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange build() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange result = new org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.sequence_ = sequence_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.localBaseKey_ = localBaseKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.localBaseKeyPrivate_ = localBaseKeyPrivate_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.localEphemeralKey_ = localEphemeralKey_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.localEphemeralKeyPrivate_ = localEphemeralKeyPrivate_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.localIdentityKey_ = localIdentityKey_; + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000040; + } + result.localIdentityKeyPrivate_ = localIdentityKeyPrivate_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance()) return this; + if (other.hasSequence()) { + setSequence(other.getSequence()); + } + if (other.hasLocalBaseKey()) { + setLocalBaseKey(other.getLocalBaseKey()); + } + if (other.hasLocalBaseKeyPrivate()) { + setLocalBaseKeyPrivate(other.getLocalBaseKeyPrivate()); + } + if (other.hasLocalEphemeralKey()) { + setLocalEphemeralKey(other.getLocalEphemeralKey()); + } + if (other.hasLocalEphemeralKeyPrivate()) { + setLocalEphemeralKeyPrivate(other.getLocalEphemeralKeyPrivate()); + } + if (other.hasLocalIdentityKey()) { + setLocalIdentityKey(other.getLocalIdentityKey()); + } + if (other.hasLocalIdentityKeyPrivate()) { + setLocalIdentityKeyPrivate(other.getLocalIdentityKeyPrivate()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + sequence_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + localBaseKey_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + localBaseKeyPrivate_ = input.readBytes(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + localEphemeralKey_ = input.readBytes(); + break; + } + case 42: { + bitField0_ |= 0x00000010; + localEphemeralKeyPrivate_ = input.readBytes(); + break; + } + case 58: { + bitField0_ |= 0x00000020; + localIdentityKey_ = input.readBytes(); + break; + } + case 66: { + bitField0_ |= 0x00000040; + localIdentityKeyPrivate_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 sequence = 1; + private int sequence_ ; + public boolean hasSequence() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getSequence() { + return sequence_; + } + public Builder setSequence(int value) { + bitField0_ |= 0x00000001; + sequence_ = value; + onChanged(); + return this; + } + public Builder clearSequence() { + bitField0_ = (bitField0_ & ~0x00000001); + sequence_ = 0; + onChanged(); + return this; + } + + // optional bytes localBaseKey = 2; + private com.google.protobuf.ByteString localBaseKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getLocalBaseKey() { + return localBaseKey_; + } + public Builder setLocalBaseKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + localBaseKey_ = value; + onChanged(); + return this; + } + public Builder clearLocalBaseKey() { + bitField0_ = (bitField0_ & ~0x00000002); + localBaseKey_ = getDefaultInstance().getLocalBaseKey(); + onChanged(); + return this; + } + + // optional bytes localBaseKeyPrivate = 3; + private com.google.protobuf.ByteString localBaseKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalBaseKeyPrivate() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getLocalBaseKeyPrivate() { + return localBaseKeyPrivate_; + } + public Builder setLocalBaseKeyPrivate(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + localBaseKeyPrivate_ = value; + onChanged(); + return this; + } + public Builder clearLocalBaseKeyPrivate() { + bitField0_ = (bitField0_ & ~0x00000004); + localBaseKeyPrivate_ = getDefaultInstance().getLocalBaseKeyPrivate(); + onChanged(); + return this; + } + + // optional bytes localEphemeralKey = 4; + private com.google.protobuf.ByteString localEphemeralKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalEphemeralKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getLocalEphemeralKey() { + return localEphemeralKey_; + } + public Builder setLocalEphemeralKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + localEphemeralKey_ = value; + onChanged(); + return this; + } + public Builder clearLocalEphemeralKey() { + bitField0_ = (bitField0_ & ~0x00000008); + localEphemeralKey_ = getDefaultInstance().getLocalEphemeralKey(); + onChanged(); + return this; + } + + // optional bytes localEphemeralKeyPrivate = 5; + private com.google.protobuf.ByteString localEphemeralKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalEphemeralKeyPrivate() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public com.google.protobuf.ByteString getLocalEphemeralKeyPrivate() { + return localEphemeralKeyPrivate_; + } + public Builder setLocalEphemeralKeyPrivate(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + localEphemeralKeyPrivate_ = value; + onChanged(); + return this; + } + public Builder clearLocalEphemeralKeyPrivate() { + bitField0_ = (bitField0_ & ~0x00000010); + localEphemeralKeyPrivate_ = getDefaultInstance().getLocalEphemeralKeyPrivate(); + onChanged(); + return this; + } + + // optional bytes localIdentityKey = 7; + private com.google.protobuf.ByteString localIdentityKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalIdentityKey() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public com.google.protobuf.ByteString getLocalIdentityKey() { + return localIdentityKey_; + } + public Builder setLocalIdentityKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + localIdentityKey_ = value; + onChanged(); + return this; + } + public Builder clearLocalIdentityKey() { + bitField0_ = (bitField0_ & ~0x00000020); + localIdentityKey_ = getDefaultInstance().getLocalIdentityKey(); + onChanged(); + return this; + } + + // optional bytes localIdentityKeyPrivate = 8; + private com.google.protobuf.ByteString localIdentityKeyPrivate_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalIdentityKeyPrivate() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + public com.google.protobuf.ByteString getLocalIdentityKeyPrivate() { + return localIdentityKeyPrivate_; + } + public Builder setLocalIdentityKeyPrivate(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + localIdentityKeyPrivate_ = value; + onChanged(); + return this; + } + public Builder clearLocalIdentityKeyPrivate() { + bitField0_ = (bitField0_ & ~0x00000040); + localIdentityKeyPrivate_ = getDefaultInstance().getLocalIdentityKeyPrivate(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.PendingKeyExchange) + } + + static { + defaultInstance = new PendingKeyExchange(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SessionStructure.PendingKeyExchange) + } + + public interface PendingPreKeyOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional uint32 preKeyId = 1; + boolean hasPreKeyId(); + int getPreKeyId(); + + // optional bytes baseKey = 2; + boolean hasBaseKey(); + com.google.protobuf.ByteString getBaseKey(); + } + public static final class PendingPreKey extends + com.google.protobuf.GeneratedMessage + implements PendingPreKeyOrBuilder { + // Use PendingPreKey.newBuilder() to construct. + private PendingPreKey(Builder builder) { + super(builder); + } + private PendingPreKey(boolean noInit) {} + + private static final PendingPreKey defaultInstance; + public static PendingPreKey getDefaultInstance() { + return defaultInstance; + } + + public PendingPreKey getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingPreKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable; + } + + private int bitField0_; + // optional uint32 preKeyId = 1; + public static final int PREKEYID_FIELD_NUMBER = 1; + private int preKeyId_; + public boolean hasPreKeyId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getPreKeyId() { + return preKeyId_; + } + + // optional bytes baseKey = 2; + public static final int BASEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString baseKey_; + public boolean hasBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getBaseKey() { + return baseKey_; + } + + private void initFields() { + preKeyId_ = 0; + baseKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, preKeyId_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, baseKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, preKeyId_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, baseKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingPreKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + preKeyId_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + baseKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey build() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey result = new org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.preKeyId_ = preKeyId_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.baseKey_ = baseKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance()) return this; + if (other.hasPreKeyId()) { + setPreKeyId(other.getPreKeyId()); + } + if (other.hasBaseKey()) { + setBaseKey(other.getBaseKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + preKeyId_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + baseKey_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 preKeyId = 1; + private int preKeyId_ ; + public boolean hasPreKeyId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getPreKeyId() { + return preKeyId_; + } + public Builder setPreKeyId(int value) { + bitField0_ |= 0x00000001; + preKeyId_ = value; + onChanged(); + return this; + } + public Builder clearPreKeyId() { + bitField0_ = (bitField0_ & ~0x00000001); + preKeyId_ = 0; + onChanged(); + return this; + } + + // optional bytes baseKey = 2; + private com.google.protobuf.ByteString baseKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasBaseKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getBaseKey() { + return baseKey_; + } + public Builder setBaseKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + baseKey_ = value; + onChanged(); + return this; + } + public Builder clearBaseKey() { + bitField0_ = (bitField0_ & ~0x00000002); + baseKey_ = getDefaultInstance().getBaseKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.PendingPreKey) + } + + static { + defaultInstance = new PendingPreKey(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SessionStructure.PendingPreKey) + } + + private int bitField0_; + // optional uint32 sessionVersion = 1; + public static final int SESSIONVERSION_FIELD_NUMBER = 1; + private int sessionVersion_; + public boolean hasSessionVersion() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getSessionVersion() { + return sessionVersion_; + } + + // optional bytes localIdentityPublic = 2; + public static final int LOCALIDENTITYPUBLIC_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString localIdentityPublic_; + public boolean hasLocalIdentityPublic() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getLocalIdentityPublic() { + return localIdentityPublic_; + } + + // optional bytes remoteIdentityPublic = 3; + public static final int REMOTEIDENTITYPUBLIC_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString remoteIdentityPublic_; + public boolean hasRemoteIdentityPublic() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getRemoteIdentityPublic() { + return remoteIdentityPublic_; + } + + // optional bytes rootKey = 4; + public static final int ROOTKEY_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString rootKey_; + public boolean hasRootKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getRootKey() { + return rootKey_; + } + + // optional uint32 previousCounter = 5; + public static final int PREVIOUSCOUNTER_FIELD_NUMBER = 5; + private int previousCounter_; + public boolean hasPreviousCounter() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public int getPreviousCounter() { + return previousCounter_; + } + + // optional .textsecure.SessionStructure.Chain senderChain = 6; + public static final int SENDERCHAIN_FIELD_NUMBER = 6; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain senderChain_; + public boolean hasSenderChain() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getSenderChain() { + return senderChain_; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder getSenderChainOrBuilder() { + return senderChain_; + } + + // repeated .textsecure.SessionStructure.Chain receiverChains = 7; + public static final int RECEIVERCHAINS_FIELD_NUMBER = 7; + private java.util.List receiverChains_; + public java.util.List getReceiverChainsList() { + return receiverChains_; + } + public java.util.List + getReceiverChainsOrBuilderList() { + return receiverChains_; + } + public int getReceiverChainsCount() { + return receiverChains_.size(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getReceiverChains(int index) { + return receiverChains_.get(index); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder getReceiverChainsOrBuilder( + int index) { + return receiverChains_.get(index); + } + + // optional .textsecure.SessionStructure.PendingKeyExchange pendingKeyExchange = 8; + public static final int PENDINGKEYEXCHANGE_FIELD_NUMBER = 8; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange pendingKeyExchange_; + public boolean hasPendingKeyExchange() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange getPendingKeyExchange() { + return pendingKeyExchange_; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder getPendingKeyExchangeOrBuilder() { + return pendingKeyExchange_; + } + + // optional .textsecure.SessionStructure.PendingPreKey pendingPreKey = 9; + public static final int PENDINGPREKEY_FIELD_NUMBER = 9; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey pendingPreKey_; + public boolean hasPendingPreKey() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey getPendingPreKey() { + return pendingPreKey_; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder getPendingPreKeyOrBuilder() { + return pendingPreKey_; + } + + private void initFields() { + sessionVersion_ = 0; + localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; + remoteIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; + rootKey_ = com.google.protobuf.ByteString.EMPTY; + previousCounter_ = 0; + senderChain_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance(); + receiverChains_ = java.util.Collections.emptyList(); + pendingKeyExchange_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance(); + pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt32(1, sessionVersion_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, localIdentityPublic_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, remoteIdentityPublic_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(4, rootKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeUInt32(5, previousCounter_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeMessage(6, senderChain_); + } + for (int i = 0; i < receiverChains_.size(); i++) { + output.writeMessage(7, receiverChains_.get(i)); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + output.writeMessage(8, pendingKeyExchange_); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeMessage(9, pendingPreKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, sessionVersion_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, localIdentityPublic_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, remoteIdentityPublic_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, rootKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(5, previousCounter_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(6, senderChain_); + } + for (int i = 0; i < receiverChains_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(7, receiverChains_.get(i)); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(8, pendingKeyExchange_); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(9, pendingPreKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_SessionStructure_fieldAccessorTable; + } + + // Construct using org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getSenderChainFieldBuilder(); + getReceiverChainsFieldBuilder(); + getPendingKeyExchangeFieldBuilder(); + getPendingPreKeyFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + sessionVersion_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + remoteIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + rootKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + previousCounter_ = 0; + bitField0_ = (bitField0_ & ~0x00000010); + if (senderChainBuilder_ == null) { + senderChain_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance(); + } else { + senderChainBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000020); + if (receiverChainsBuilder_ == null) { + receiverChains_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000040); + } else { + receiverChainsBuilder_.clear(); + } + if (pendingKeyExchangeBuilder_ == null) { + pendingKeyExchange_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance(); + } else { + pendingKeyExchangeBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000080); + if (pendingPreKeyBuilder_ == null) { + pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); + } else { + pendingPreKeyBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000100); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDescriptor(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getDefaultInstanceForType() { + return org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance(); + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure build() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure buildPartial() { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure result = new org.whispersystems.textsecure.storage.StorageProtos.SessionStructure(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.sessionVersion_ = sessionVersion_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.localIdentityPublic_ = localIdentityPublic_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.remoteIdentityPublic_ = remoteIdentityPublic_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.rootKey_ = rootKey_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.previousCounter_ = previousCounter_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + if (senderChainBuilder_ == null) { + result.senderChain_ = senderChain_; + } else { + result.senderChain_ = senderChainBuilder_.build(); + } + if (receiverChainsBuilder_ == null) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { + receiverChains_ = java.util.Collections.unmodifiableList(receiverChains_); + bitField0_ = (bitField0_ & ~0x00000040); + } + result.receiverChains_ = receiverChains_; + } else { + result.receiverChains_ = receiverChainsBuilder_.build(); + } + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000040; + } + if (pendingKeyExchangeBuilder_ == null) { + result.pendingKeyExchange_ = pendingKeyExchange_; + } else { + result.pendingKeyExchange_ = pendingKeyExchangeBuilder_.build(); + } + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000080; + } + if (pendingPreKeyBuilder_ == null) { + result.pendingPreKey_ = pendingPreKey_; + } else { + result.pendingPreKey_ = pendingPreKeyBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.SessionStructure) { + return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.SessionStructure)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure other) { + if (other == org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance()) return this; + if (other.hasSessionVersion()) { + setSessionVersion(other.getSessionVersion()); + } + if (other.hasLocalIdentityPublic()) { + setLocalIdentityPublic(other.getLocalIdentityPublic()); + } + if (other.hasRemoteIdentityPublic()) { + setRemoteIdentityPublic(other.getRemoteIdentityPublic()); + } + if (other.hasRootKey()) { + setRootKey(other.getRootKey()); + } + if (other.hasPreviousCounter()) { + setPreviousCounter(other.getPreviousCounter()); + } + if (other.hasSenderChain()) { + mergeSenderChain(other.getSenderChain()); + } + if (receiverChainsBuilder_ == null) { + if (!other.receiverChains_.isEmpty()) { + if (receiverChains_.isEmpty()) { + receiverChains_ = other.receiverChains_; + bitField0_ = (bitField0_ & ~0x00000040); + } else { + ensureReceiverChainsIsMutable(); + receiverChains_.addAll(other.receiverChains_); + } + onChanged(); + } + } else { + if (!other.receiverChains_.isEmpty()) { + if (receiverChainsBuilder_.isEmpty()) { + receiverChainsBuilder_.dispose(); + receiverChainsBuilder_ = null; + receiverChains_ = other.receiverChains_; + bitField0_ = (bitField0_ & ~0x00000040); + receiverChainsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getReceiverChainsFieldBuilder() : null; + } else { + receiverChainsBuilder_.addAllMessages(other.receiverChains_); + } + } + } + if (other.hasPendingKeyExchange()) { + mergePendingKeyExchange(other.getPendingKeyExchange()); + } + if (other.hasPendingPreKey()) { + mergePendingPreKey(other.getPendingPreKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + sessionVersion_ = input.readUInt32(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + localIdentityPublic_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + remoteIdentityPublic_ = input.readBytes(); + break; + } + case 34: { + bitField0_ |= 0x00000008; + rootKey_ = input.readBytes(); + break; + } + case 40: { + bitField0_ |= 0x00000010; + previousCounter_ = input.readUInt32(); + break; + } + case 50: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.newBuilder(); + if (hasSenderChain()) { + subBuilder.mergeFrom(getSenderChain()); + } + input.readMessage(subBuilder, extensionRegistry); + setSenderChain(subBuilder.buildPartial()); + break; + } + case 58: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.newBuilder(); + input.readMessage(subBuilder, extensionRegistry); + addReceiverChains(subBuilder.buildPartial()); + break; + } + case 66: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.newBuilder(); + if (hasPendingKeyExchange()) { + subBuilder.mergeFrom(getPendingKeyExchange()); + } + input.readMessage(subBuilder, extensionRegistry); + setPendingKeyExchange(subBuilder.buildPartial()); + break; + } + case 74: { + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.newBuilder(); + if (hasPendingPreKey()) { + subBuilder.mergeFrom(getPendingPreKey()); + } + input.readMessage(subBuilder, extensionRegistry); + setPendingPreKey(subBuilder.buildPartial()); + break; + } + } + } + } + + private int bitField0_; + + // optional uint32 sessionVersion = 1; + private int sessionVersion_ ; + public boolean hasSessionVersion() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public int getSessionVersion() { + return sessionVersion_; + } + public Builder setSessionVersion(int value) { + bitField0_ |= 0x00000001; + sessionVersion_ = value; + onChanged(); + return this; + } + public Builder clearSessionVersion() { + bitField0_ = (bitField0_ & ~0x00000001); + sessionVersion_ = 0; + onChanged(); + return this; + } + + // optional bytes localIdentityPublic = 2; + private com.google.protobuf.ByteString localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasLocalIdentityPublic() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getLocalIdentityPublic() { + return localIdentityPublic_; + } + public Builder setLocalIdentityPublic(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + localIdentityPublic_ = value; + onChanged(); + return this; + } + public Builder clearLocalIdentityPublic() { + bitField0_ = (bitField0_ & ~0x00000002); + localIdentityPublic_ = getDefaultInstance().getLocalIdentityPublic(); + onChanged(); + return this; + } + + // optional bytes remoteIdentityPublic = 3; + private com.google.protobuf.ByteString remoteIdentityPublic_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasRemoteIdentityPublic() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public com.google.protobuf.ByteString getRemoteIdentityPublic() { + return remoteIdentityPublic_; + } + public Builder setRemoteIdentityPublic(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + remoteIdentityPublic_ = value; + onChanged(); + return this; + } + public Builder clearRemoteIdentityPublic() { + bitField0_ = (bitField0_ & ~0x00000004); + remoteIdentityPublic_ = getDefaultInstance().getRemoteIdentityPublic(); + onChanged(); + return this; + } + + // optional bytes rootKey = 4; + private com.google.protobuf.ByteString rootKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasRootKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public com.google.protobuf.ByteString getRootKey() { + return rootKey_; + } + public Builder setRootKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + rootKey_ = value; + onChanged(); + return this; + } + public Builder clearRootKey() { + bitField0_ = (bitField0_ & ~0x00000008); + rootKey_ = getDefaultInstance().getRootKey(); + onChanged(); + return this; + } + + // optional uint32 previousCounter = 5; + private int previousCounter_ ; + public boolean hasPreviousCounter() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public int getPreviousCounter() { + return previousCounter_; + } + public Builder setPreviousCounter(int value) { + bitField0_ |= 0x00000010; + previousCounter_ = value; + onChanged(); + return this; + } + public Builder clearPreviousCounter() { + bitField0_ = (bitField0_ & ~0x00000010); + previousCounter_ = 0; + onChanged(); + return this; + } + + // optional .textsecure.SessionStructure.Chain senderChain = 6; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain senderChain_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder> senderChainBuilder_; + public boolean hasSenderChain() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getSenderChain() { + if (senderChainBuilder_ == null) { + return senderChain_; + } else { + return senderChainBuilder_.getMessage(); + } + } + public Builder setSenderChain(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain value) { + if (senderChainBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + senderChain_ = value; + onChanged(); + } else { + senderChainBuilder_.setMessage(value); + } + bitField0_ |= 0x00000020; + return this; + } + public Builder setSenderChain( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder builderForValue) { + if (senderChainBuilder_ == null) { + senderChain_ = builderForValue.build(); + onChanged(); + } else { + senderChainBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000020; + return this; + } + public Builder mergeSenderChain(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain value) { + if (senderChainBuilder_ == null) { + if (((bitField0_ & 0x00000020) == 0x00000020) && + senderChain_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance()) { + senderChain_ = + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.newBuilder(senderChain_).mergeFrom(value).buildPartial(); + } else { + senderChain_ = value; + } + onChanged(); + } else { + senderChainBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000020; + return this; + } + public Builder clearSenderChain() { + if (senderChainBuilder_ == null) { + senderChain_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance(); + onChanged(); + } else { + senderChainBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder getSenderChainBuilder() { + bitField0_ |= 0x00000020; + onChanged(); + return getSenderChainFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder getSenderChainOrBuilder() { + if (senderChainBuilder_ != null) { + return senderChainBuilder_.getMessageOrBuilder(); + } else { + return senderChain_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder> + getSenderChainFieldBuilder() { + if (senderChainBuilder_ == null) { + senderChainBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder>( + senderChain_, + getParentForChildren(), + isClean()); + senderChain_ = null; + } + return senderChainBuilder_; + } + + // repeated .textsecure.SessionStructure.Chain receiverChains = 7; + private java.util.List receiverChains_ = + java.util.Collections.emptyList(); + private void ensureReceiverChainsIsMutable() { + if (!((bitField0_ & 0x00000040) == 0x00000040)) { + receiverChains_ = new java.util.ArrayList(receiverChains_); + bitField0_ |= 0x00000040; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder> receiverChainsBuilder_; + + public java.util.List getReceiverChainsList() { + if (receiverChainsBuilder_ == null) { + return java.util.Collections.unmodifiableList(receiverChains_); + } else { + return receiverChainsBuilder_.getMessageList(); + } + } + public int getReceiverChainsCount() { + if (receiverChainsBuilder_ == null) { + return receiverChains_.size(); + } else { + return receiverChainsBuilder_.getCount(); + } + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain getReceiverChains(int index) { + if (receiverChainsBuilder_ == null) { + return receiverChains_.get(index); + } else { + return receiverChainsBuilder_.getMessage(index); + } + } + public Builder setReceiverChains( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain value) { + if (receiverChainsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureReceiverChainsIsMutable(); + receiverChains_.set(index, value); + onChanged(); + } else { + receiverChainsBuilder_.setMessage(index, value); + } + return this; + } + public Builder setReceiverChains( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder builderForValue) { + if (receiverChainsBuilder_ == null) { + ensureReceiverChainsIsMutable(); + receiverChains_.set(index, builderForValue.build()); + onChanged(); + } else { + receiverChainsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + public Builder addReceiverChains(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain value) { + if (receiverChainsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureReceiverChainsIsMutable(); + receiverChains_.add(value); + onChanged(); + } else { + receiverChainsBuilder_.addMessage(value); + } + return this; + } + public Builder addReceiverChains( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain value) { + if (receiverChainsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureReceiverChainsIsMutable(); + receiverChains_.add(index, value); + onChanged(); + } else { + receiverChainsBuilder_.addMessage(index, value); + } + return this; + } + public Builder addReceiverChains( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder builderForValue) { + if (receiverChainsBuilder_ == null) { + ensureReceiverChainsIsMutable(); + receiverChains_.add(builderForValue.build()); + onChanged(); + } else { + receiverChainsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + public Builder addReceiverChains( + int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder builderForValue) { + if (receiverChainsBuilder_ == null) { + ensureReceiverChainsIsMutable(); + receiverChains_.add(index, builderForValue.build()); + onChanged(); + } else { + receiverChainsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + public Builder addAllReceiverChains( + java.lang.Iterable values) { + if (receiverChainsBuilder_ == null) { + ensureReceiverChainsIsMutable(); + super.addAll(values, receiverChains_); + onChanged(); + } else { + receiverChainsBuilder_.addAllMessages(values); + } + return this; + } + public Builder clearReceiverChains() { + if (receiverChainsBuilder_ == null) { + receiverChains_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000040); + onChanged(); + } else { + receiverChainsBuilder_.clear(); + } + return this; + } + public Builder removeReceiverChains(int index) { + if (receiverChainsBuilder_ == null) { + ensureReceiverChainsIsMutable(); + receiverChains_.remove(index); + onChanged(); + } else { + receiverChainsBuilder_.remove(index); + } + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder getReceiverChainsBuilder( + int index) { + return getReceiverChainsFieldBuilder().getBuilder(index); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder getReceiverChainsOrBuilder( + int index) { + if (receiverChainsBuilder_ == null) { + return receiverChains_.get(index); } else { + return receiverChainsBuilder_.getMessageOrBuilder(index); + } + } + public java.util.List + getReceiverChainsOrBuilderList() { + if (receiverChainsBuilder_ != null) { + return receiverChainsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(receiverChains_); + } + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder addReceiverChainsBuilder() { + return getReceiverChainsFieldBuilder().addBuilder( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance()); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder addReceiverChainsBuilder( + int index) { + return getReceiverChainsFieldBuilder().addBuilder( + index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.getDefaultInstance()); + } + public java.util.List + getReceiverChainsBuilderList() { + return getReceiverChainsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder> + getReceiverChainsFieldBuilder() { + if (receiverChainsBuilder_ == null) { + receiverChainsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.ChainOrBuilder>( + receiverChains_, + ((bitField0_ & 0x00000040) == 0x00000040), + getParentForChildren(), + isClean()); + receiverChains_ = null; + } + return receiverChainsBuilder_; + } + + // optional .textsecure.SessionStructure.PendingKeyExchange pendingKeyExchange = 8; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange pendingKeyExchange_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder> pendingKeyExchangeBuilder_; + public boolean hasPendingKeyExchange() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange getPendingKeyExchange() { + if (pendingKeyExchangeBuilder_ == null) { + return pendingKeyExchange_; + } else { + return pendingKeyExchangeBuilder_.getMessage(); + } + } + public Builder setPendingKeyExchange(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange value) { + if (pendingKeyExchangeBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + pendingKeyExchange_ = value; + onChanged(); + } else { + pendingKeyExchangeBuilder_.setMessage(value); + } + bitField0_ |= 0x00000080; + return this; + } + public Builder setPendingKeyExchange( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder builderForValue) { + if (pendingKeyExchangeBuilder_ == null) { + pendingKeyExchange_ = builderForValue.build(); + onChanged(); + } else { + pendingKeyExchangeBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000080; + return this; + } + public Builder mergePendingKeyExchange(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange value) { + if (pendingKeyExchangeBuilder_ == null) { + if (((bitField0_ & 0x00000080) == 0x00000080) && + pendingKeyExchange_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance()) { + pendingKeyExchange_ = + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.newBuilder(pendingKeyExchange_).mergeFrom(value).buildPartial(); + } else { + pendingKeyExchange_ = value; + } + onChanged(); + } else { + pendingKeyExchangeBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000080; + return this; + } + public Builder clearPendingKeyExchange() { + if (pendingKeyExchangeBuilder_ == null) { + pendingKeyExchange_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.getDefaultInstance(); + onChanged(); + } else { + pendingKeyExchangeBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000080); + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder getPendingKeyExchangeBuilder() { + bitField0_ |= 0x00000080; + onChanged(); + return getPendingKeyExchangeFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder getPendingKeyExchangeOrBuilder() { + if (pendingKeyExchangeBuilder_ != null) { + return pendingKeyExchangeBuilder_.getMessageOrBuilder(); + } else { + return pendingKeyExchange_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder> + getPendingKeyExchangeFieldBuilder() { + if (pendingKeyExchangeBuilder_ == null) { + pendingKeyExchangeBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchangeOrBuilder>( + pendingKeyExchange_, + getParentForChildren(), + isClean()); + pendingKeyExchange_ = null; + } + return pendingKeyExchangeBuilder_; + } + + // optional .textsecure.SessionStructure.PendingPreKey pendingPreKey = 9; + private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder> pendingPreKeyBuilder_; + public boolean hasPendingPreKey() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey getPendingPreKey() { + if (pendingPreKeyBuilder_ == null) { + return pendingPreKey_; + } else { + return pendingPreKeyBuilder_.getMessage(); + } + } + public Builder setPendingPreKey(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey value) { + if (pendingPreKeyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + pendingPreKey_ = value; + onChanged(); + } else { + pendingPreKeyBuilder_.setMessage(value); + } + bitField0_ |= 0x00000100; + return this; + } + public Builder setPendingPreKey( + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder builderForValue) { + if (pendingPreKeyBuilder_ == null) { + pendingPreKey_ = builderForValue.build(); + onChanged(); + } else { + pendingPreKeyBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000100; + return this; + } + public Builder mergePendingPreKey(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey value) { + if (pendingPreKeyBuilder_ == null) { + if (((bitField0_ & 0x00000100) == 0x00000100) && + pendingPreKey_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance()) { + pendingPreKey_ = + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.newBuilder(pendingPreKey_).mergeFrom(value).buildPartial(); + } else { + pendingPreKey_ = value; + } + onChanged(); + } else { + pendingPreKeyBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000100; + return this; + } + public Builder clearPendingPreKey() { + if (pendingPreKeyBuilder_ == null) { + pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance(); + onChanged(); + } else { + pendingPreKeyBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000100); + return this; + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder getPendingPreKeyBuilder() { + bitField0_ |= 0x00000100; + onChanged(); + return getPendingPreKeyFieldBuilder().getBuilder(); + } + public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder getPendingPreKeyOrBuilder() { + if (pendingPreKeyBuilder_ != null) { + return pendingPreKeyBuilder_.getMessageOrBuilder(); + } else { + return pendingPreKey_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder> + getPendingPreKeyFieldBuilder() { + if (pendingPreKeyBuilder_ == null) { + pendingPreKeyBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKeyOrBuilder>( + pendingPreKey_, + getParentForChildren(), + isClean()); + pendingPreKey_ = null; + } + return pendingPreKeyBuilder_; + } + + // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure) + } + + static { + defaultInstance = new SessionStructure(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:textsecure.SessionStructure) + } + + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SessionStructure_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SessionStructure_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SessionStructure_Chain_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SessionStructure_Chain_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SessionStructure_Chain_ChainKey_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SessionStructure_Chain_ChainKey_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SessionStructure_PendingKeyExchange_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_textsecure_SessionStructure_PendingPreKey_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\032LocalStorageProtocol.proto\022\ntextsecure" + + "\"\312\007\n\020SessionStructure\022\026\n\016sessionVersion\030" + + "\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" + + "moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" + + "\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" + + "n\030\006 \001(\0132\".textsecure.SessionStructure.Ch" + + "ain\022:\n\016receiverChains\030\007 \003(\0132\".textsecure" + + ".SessionStructure.Chain\022K\n\022pendingKeyExc" + + "hange\030\010 \001(\0132/.textsecure.SessionStructur" + + "e.PendingKeyExchange\022A\n\rpendingPreKey\030\t ", + "\001(\0132*.textsecure.SessionStructure.Pendin" + + "gPreKey\032\253\002\n\005Chain\022\027\n\017senderEphemeral\030\001 \001" + + "(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022=\n\010ch" + + "ainKey\030\003 \001(\0132+.textsecure.SessionStructu" + + "re.Chain.ChainKey\022B\n\013messageKeys\030\004 \003(\0132-" + + ".textsecure.SessionStructure.Chain.Messa" + + "geKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n\003key\030" + + "\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022\021\n\tci" + + "pherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022Pendin" + + "gKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014localB", + "aseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001(" + + "\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030localEph" + + "emeralKeyPrivate\030\005 \001(\014\022\030\n\020localIdentityK" + + "ey\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate\030\010 \001(" + + "\014\0322\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\017\n\007" + + "baseKey\030\002 \001(\014B6\n%org.whispersystems.text" + + "secure.storageB\rStorageProtos" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + internal_static_textsecure_SessionStructure_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_textsecure_SessionStructure_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SessionStructure_descriptor, + new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", }, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.class, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class); + internal_static_textsecure_SessionStructure_Chain_descriptor = + internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(0); + internal_static_textsecure_SessionStructure_Chain_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SessionStructure_Chain_descriptor, + new java.lang.String[] { "SenderEphemeral", "SenderEphemeralPrivate", "ChainKey", "MessageKeys", }, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.class, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.Builder.class); + internal_static_textsecure_SessionStructure_Chain_ChainKey_descriptor = + internal_static_textsecure_SessionStructure_Chain_descriptor.getNestedTypes().get(0); + internal_static_textsecure_SessionStructure_Chain_ChainKey_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SessionStructure_Chain_ChainKey_descriptor, + new java.lang.String[] { "Index", "Key", }, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.class, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.ChainKey.Builder.class); + internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor = + internal_static_textsecure_SessionStructure_Chain_descriptor.getNestedTypes().get(1); + internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor, + new java.lang.String[] { "Index", "CipherKey", "MacKey", }, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.class, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain.MessageKey.Builder.class); + internal_static_textsecure_SessionStructure_PendingKeyExchange_descriptor = + internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(1); + internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SessionStructure_PendingKeyExchange_descriptor, + new java.lang.String[] { "Sequence", "LocalBaseKey", "LocalBaseKeyPrivate", "LocalEphemeralKey", "LocalEphemeralKeyPrivate", "LocalIdentityKey", "LocalIdentityKeyPrivate", }, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.class, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange.Builder.class); + internal_static_textsecure_SessionStructure_PendingPreKey_descriptor = + internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(2); + internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_textsecure_SessionStructure_PendingPreKey_descriptor, + new java.lang.String[] { "PreKeyId", "BaseKey", }, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.class, + org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder.class); + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/res/menu/conversation_secure_no_identity.xml b/res/menu/conversation_secure_no_identity.xml index fb84902e79..ab7cf31658 100644 --- a/res/menu/conversation_secure_no_identity.xml +++ b/res/menu/conversation_secure_no_identity.xml @@ -5,8 +5,9 @@ android:icon="@drawable/ic_menu_lock_holo_dark" android:showAsAction="ifRoom"> - + diff --git a/res/values/strings.xml b/res/values/strings.xml index aceeb60fda..a058968903 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -688,7 +688,7 @@ Security - Verify Session + No Identity Available Verify Recipient Abort Secure Session diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index ee6af84c40..1bcf9f2ae6 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -96,7 +96,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr initializeIdentitySelection(); initializePlatformSpecificOptions(); initializePushMessagingToggle(); - initializeEditTextSummaries(); this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF) .setOnPreferenceClickListener(new ChangePassphraseClickListener()); @@ -210,12 +209,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredSherlockPr }); } - private void initializeEditTextSummaries() { - initializeEditTextSummary((EditTextPreference)this.findPreference(TextSecurePreferences.MMSC_HOST_PREF)); - initializeEditTextSummary((EditTextPreference)this.findPreference(TextSecurePreferences.MMSC_PROXY_HOST_PREF)); - initializeEditTextSummary((EditTextPreference)this.findPreference(TextSecurePreferences.MMSC_PROXY_PORT_PREF)); - } - private void initializePushMessagingToggle() { CheckBoxPreference preference = (CheckBoxPreference)this.findPreference(PUSH_MESSAGING_PREF); preference.setChecked(TextSecurePreferences.isPushRegistered(this)); diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index e67bdc68cf..c13e926781 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -51,18 +51,14 @@ import android.widget.Toast; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; + import org.thoughtcrime.securesms.components.EmojiDrawer; import org.thoughtcrime.securesms.components.EmojiToggle; import org.thoughtcrime.securesms.components.RecipientsPanel; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; -import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData; import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; -import org.whispersystems.textsecure.crypto.InvalidMessageException; -import org.whispersystems.textsecure.crypto.KeyUtil; -import org.whispersystems.textsecure.crypto.MasterCipher; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase.Draft; @@ -89,6 +85,10 @@ import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator; import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterCipher; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.util.Util; import java.io.IOException; @@ -275,7 +275,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi case R.id.menu_start_secure_session: handleStartSecureSession(); return true; case R.id.menu_abort_session: handleAbortSecureSession(); return true; case R.id.menu_verify_recipient: handleVerifyRecipient(); return true; - case R.id.menu_verify_session: handleVerifySession(); return true; case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; @@ -329,13 +328,6 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi startActivity(verifyIdentityIntent); } - private void handleVerifySession() { - Intent verifyKeysIntent = new Intent(this, VerifyKeysActivity.class); - verifyKeysIntent.putExtra("recipient", getRecipients().getPrimaryRecipient()); - verifyKeysIntent.putExtra("master_secret", masterSecret); - startActivity(verifyKeysIntent); - } - private void handleStartSecureSession() { if (getRecipients() == null) { Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient), @@ -373,7 +365,7 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi @Override public void onClick(DialogInterface dialog, int which) { if (isSingleConversation()) { - KeyUtil.abortSessionFor(ConversationActivity.this, getRecipients().getPrimaryRecipient()); + Session.abortSessionFor(ConversationActivity.this, getRecipients().getPrimaryRecipient()); initializeSecurity(); initializeTitleBar(); } @@ -567,11 +559,11 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi TypedArray drawables = obtainStyledAttributes(attributes); if (isSingleConversation() && - KeyUtil.isSessionFor(this, getRecipients().getPrimaryRecipient())) + Session.hasSession(this, masterSecret, getRecipients().getPrimaryRecipient())) { sendButton.setImageDrawable(drawables.getDrawable(1)); this.isEncryptedConversation = true; - this.isAuthenticatedConversation = KeyUtil.isIdentityKeyFor(this, masterSecret, getRecipients().getPrimaryRecipient()); + this.isAuthenticatedConversation = Session.hasRemoteIdentityKey(this, masterSecret, getRecipients().getPrimaryRecipient()); this.characterCalculator = new EncryptedCharacterCalculator(); } else { sendButton.setImageDrawable(drawables.getDrawable(0)); diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index 7ce6cb7bd2..d795695e08 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -31,17 +31,19 @@ import android.widget.Button; import android.widget.TextView; import org.thoughtcrime.securesms.crypto.DecryptingQueue; -import org.thoughtcrime.securesms.sms.SmsTransportDetails; -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; -import org.whispersystems.textsecure.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.MemoryCleaner; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import java.io.IOException; @@ -64,9 +66,8 @@ public class ReceiveKeyActivity extends Activity { private long messageId; private MasterSecret masterSecret; - private PreKeyBundleMessage keyExchangeMessageBundle; + private PreKeyWhisperMessage keyExchangeMessageBundle; private KeyExchangeMessage keyExchangeMessage; - private KeyExchangeProcessor keyExchangeProcessor; @Override protected void onCreate(Bundle state) { @@ -82,6 +83,8 @@ public class ReceiveKeyActivity extends Activity { Log.w("ReceiveKeyActivity", ike); } catch (InvalidVersionException ive) { Log.w("ReceiveKeyActivity", ive); + } catch (InvalidMessageException e) { + Log.w("ReceiveKeyActivity", e); } initializeListeners(); } @@ -122,12 +125,22 @@ public class ReceiveKeyActivity extends Activity { descriptionText.setMovementMethod(LinkMovementMethod.getInstance()); } - private boolean isTrusted(KeyExchangeMessage message, PreKeyBundleMessage messageBundle) { - return (message != null && keyExchangeProcessor.isTrusted(message)) || - (messageBundle != null && keyExchangeProcessor.isTrusted(messageBundle)); + private boolean isTrusted(KeyExchangeMessage message, PreKeyWhisperMessage messageBundle) { + if (message != null) { + KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(this, masterSecret, + recipient, message); + return processor.isTrusted(message); + } else if (messageBundle != null) { + KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(this, masterSecret, recipient); + return processor.isTrusted(messageBundle); + } + + return false; } - private void initializeKey() throws InvalidKeyException, InvalidVersionException { + private void initializeKey() + throws InvalidKeyException, InvalidVersionException, InvalidMessageException + { try { String messageBody = getIntent().getStringExtra("body"); @@ -135,9 +148,9 @@ public class ReceiveKeyActivity extends Activity { SmsTransportDetails transportDetails = new SmsTransportDetails(); byte[] body = transportDetails.getDecodedMessage(messageBody.getBytes()); - this.keyExchangeMessageBundle = new PreKeyBundleMessage(body); + this.keyExchangeMessageBundle = new PreKeyWhisperMessage(body); } else { - this.keyExchangeMessage = new KeyExchangeMessage(messageBody); + this.keyExchangeMessage = KeyExchangeMessage.createFor(messageBody); } } catch (IOException e) { throw new AssertionError(e); @@ -152,7 +165,6 @@ public class ReceiveKeyActivity extends Activity { this.threadId = getIntent().getLongExtra("thread_id", -1); this.messageId = getIntent().getLongExtra("message_id", -1); this.masterSecret = getIntent().getParcelableExtra("master_secret"); - this.keyExchangeProcessor = new KeyExchangeProcessor(this, masterSecret, recipient); } private void initializeListeners() { @@ -177,13 +189,23 @@ public class ReceiveKeyActivity extends Activity { @Override protected Void doInBackground(Void... params) { if (keyExchangeMessage != null) { - keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessage, threadId); - DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) - .markAsProcessedKeyExchange(messageId); + try { + KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(ReceiveKeyActivity.this, masterSecret, recipient, keyExchangeMessage); + processor.processKeyExchangeMessage(keyExchangeMessage, threadId); + DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) + .markAsProcessedKeyExchange(messageId); + } catch (InvalidMessageException e) { + Log.w("ReceiveKeyActivity", e); + DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) + .markAsCorruptKeyExchange(messageId); + } } else if (keyExchangeMessageBundle != null) { try { - keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessageBundle); - CiphertextMessage bundledMessage = keyExchangeMessageBundle.getBundledMessage(); + KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(ReceiveKeyActivity.this, + masterSecret, recipient); + processor.processKeyExchangeMessage(keyExchangeMessageBundle); + + CiphertextMessage bundledMessage = keyExchangeMessageBundle.getWhisperMessage(); SmsTransportDetails transportDetails = new SmsTransportDetails(); String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage.serialize())); diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index cd7ad31dd0..3af573dd82 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -21,15 +21,14 @@ import android.os.Bundle; import android.widget.TextView; import android.widget.Toast; -import org.whispersystems.textsecure.crypto.IdentityKey; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.storage.SessionRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.MemoryCleaner; +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; +import org.whispersystems.textsecure.storage.Session; /** * Activity for verifying identity keys. @@ -68,15 +67,14 @@ public class VerifyIdentityActivity extends KeyScanningActivity { return; } - localIdentityFingerprint.setText(IdentityKeyUtil.getFingerprint(this, keyType)); + localIdentityFingerprint.setText(IdentityKeyUtil.getIdentityKey(this, keyType).getFingerprint()); } private void initializeRemoteIdentityKey() { IdentityKey identityKey = getIntent().getParcelableExtra("remote_identity"); if (identityKey == null) { - SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient); - identityKey = sessionRecord.getIdentityKey(); + identityKey = Session.getRemoteIdentityKey(this, masterSecret, recipient); } if (identityKey == null) { @@ -97,13 +95,12 @@ public class VerifyIdentityActivity extends KeyScanningActivity { this.recipient = this.getIntent().getParcelableExtra("recipient"); this.masterSecret = this.getIntent().getParcelableExtra("master_secret"); - SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient); - int sessionVersion = sessionRecord.getSessionVersion(); + int sessionVersion = Session.getSessionVersion(this, masterSecret, recipient); - if (sessionVersion >= CiphertextMessage.CURVE25519_INTRODUCED_VERSION) { - this.keyType = Curve.DJB_TYPE; - } else { + if (sessionVersion <= CiphertextMessage.LEGACY_VERSION) { this.keyType = Curve.NIST_TYPE; + } else { + this.keyType = Curve.DJB_TYPE; } } @@ -121,8 +118,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity { @Override protected void initiateScan() { - SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient); - IdentityKey identityKey = sessionRecord.getIdentityKey(); + IdentityKey identityKey = Session.getRemoteIdentityKey(this, masterSecret, recipient); if (identityKey == null) { Toast.makeText(this, R.string.VerifyIdentityActivity_recipient_has_no_identity_key_exclamation, @@ -144,8 +140,7 @@ public class VerifyIdentityActivity extends KeyScanningActivity { @Override protected IdentityKey getIdentityKeyToCompare() { - SessionRecord sessionRecord = new SessionRecord(this, masterSecret, recipient); - return sessionRecord.getIdentityKey(); + return Session.getRemoteIdentityKey(this, masterSecret, recipient); } @Override diff --git a/src/org/thoughtcrime/securesms/VerifyKeysActivity.java b/src/org/thoughtcrime/securesms/VerifyKeysActivity.java deleted file mode 100644 index 9c7f3f6b51..0000000000 --- a/src/org/thoughtcrime/securesms/VerifyKeysActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms; - -import android.os.Bundle; -import android.widget.TextView; - -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.SerializableKey; -import org.whispersystems.textsecure.storage.SessionRecord; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.whispersystems.textsecure.util.Hex; -import org.thoughtcrime.securesms.util.MemoryCleaner; - -/** - * Activity for verifying session keys. - * - * @author Moxie Marlinspike - * - */ -public class VerifyKeysActivity extends KeyScanningActivity { - - private byte[] yourFingerprintBytes; - private byte[] theirFingerprintBytes; - - private TextView yourFingerprint; - private TextView theirFingerprint; - - private Recipient recipient; - private MasterSecret masterSecret; - - @Override - protected void onCreate(Bundle state) { - super.onCreate(state); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.verify_keys_activity); - - initializeResources(); - initializeFingerprints(); - } - - @Override - protected void onDestroy() { - MemoryCleaner.clean(masterSecret); - super.onDestroy(); - } - - private void initializeResources() { - this.recipient = (Recipient)this.getIntent().getParcelableExtra("recipient"); - this.masterSecret = (MasterSecret)this.getIntent().getParcelableExtra("master_secret"); - this.yourFingerprint = (TextView)findViewById(R.id.you_read); - this.theirFingerprint = (TextView)findViewById(R.id.friend_reads); - } - - private void initializeFingerprints() { - SessionRecord session = new SessionRecord(this, masterSecret, recipient); - this.yourFingerprintBytes = session.getLocalFingerprint(); - this.theirFingerprintBytes = session.getRemoteFingerprint(); - - this.yourFingerprint.setText(Hex.toString(yourFingerprintBytes)); - this.theirFingerprint.setText(Hex.toString(theirFingerprintBytes)); - } - - @Override - protected String getDisplayString() { - return getString(R.string.VerifyKeysActivity_get_my_fingerprint_scanned); - } - - @Override - protected String getScanString() { - return getString(R.string.VerifyKeysActivity_scan_their_fingerprint); - } - - @Override - protected SerializableKey getIdentityKeyToCompare() { - return new FingerprintKey(theirFingerprintBytes); - } - - @Override - protected SerializableKey getIdentityKeyToDisplay() { - return new FingerprintKey(yourFingerprintBytes); - } - - @Override - protected String getNotVerifiedMessage() { - return getString(R.string.VerifyKeysActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully2); - } - - @Override - protected String getNotVerifiedTitle() { - return getString(R.string.VerifyKeysActivity_not_verified_exclamation); - } - - @Override - protected String getVerifiedMessage() { - return getString(R.string.VerifyKeysActivity_their_key_is_correct_it_is_also_necessary_to_get_your_fingerprint_scanned_as_well); - } - - @Override - protected String getVerifiedTitle() { - return getString(R.string.VerifyKeysActivity_verified_exclamation); - } - - private class FingerprintKey implements SerializableKey { - private final byte[] fingerprint; - - public FingerprintKey(byte[] fingerprint) { - this.fingerprint = fingerprint; - } - - public byte[] serialize() { - return fingerprint; - } - } - -} diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index c0b825e54f..0595a6454b 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -40,18 +40,15 @@ import org.thoughtcrime.securesms.service.PushReceiver; import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.SessionCipher; -import org.whispersystems.textsecure.crypto.ecc.Curve; -import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.util.Util; import java.io.IOException; import java.util.concurrent.Executor; @@ -188,29 +185,26 @@ public class DecryptingQueue { } public void run() { - synchronized (SessionCipher.CIPHER_LOCK) { - try { - Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false); - Recipient recipient = recipients.getPrimaryRecipient(); + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSource(), false); + Recipient recipient = recipients.getPrimaryRecipient(); - if (!KeyUtil.isSessionFor(context, recipient)) { - sendResult(PushReceiver.RESULT_NO_SESSION); - return; - } - - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); - byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody()); - - message = message.withBody(plaintextBody); - sendResult(PushReceiver.RESULT_OK); - } catch (InvalidMessageException e) { - Log.w("DecryptionQueue", e); - sendResult(PushReceiver.RESULT_DECRYPT_FAILED); - } catch (RecipientFormattingException e) { - Log.w("DecryptionQueue", e); - sendResult(PushReceiver.RESULT_DECRYPT_FAILED); + if (!Session.hasSession(context, masterSecret, recipient)) { + sendResult(PushReceiver.RESULT_NO_SESSION); + return; } + + SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient); + byte[] plaintextBody = sessionCipher.decrypt(message.getBody()); + + message = message.withBody(plaintextBody); + sendResult(PushReceiver.RESULT_OK); + } catch (InvalidMessageException e) { + Log.w("DecryptionQueue", e); + sendResult(PushReceiver.RESULT_DECRYPT_FAILED); + } catch (RecipientFormattingException e) { + Log.w("DecryptionQueue", e); + sendResult(PushReceiver.RESULT_DECRYPT_FAILED); } } @@ -268,7 +262,7 @@ public class DecryptingQueue { return; } - if (!KeyUtil.isSessionFor(context, recipient)) { + if (!Session.hasSession(context, masterSecret, recipient)) { Log.w("DecryptingQueue", "No such recipient session for MMS..."); database.markAsNoSession(messageId, threadId); return; @@ -276,28 +270,24 @@ public class DecryptingQueue { byte[] plaintextPduBytes; - synchronized (SessionCipher.CIPHER_LOCK) { - Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); - TextTransport transportDetails = new TextTransport(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); - byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes); + Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); + TextTransport transportDetails = new TextTransport(); + SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient); + byte[] decodedCiphertext = transportDetails.getDecodedMessage(ciphertextPduBytes); - try { - plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext); - } catch (InvalidMessageException ime) { - // XXX - For some reason, Sprint seems to append a single character to the - // end of message text segments. I don't know why, so here we just try - // truncating the message by one if the MAC fails. - if (ciphertextPduBytes.length > 2) { - Log.w("DecryptingQueue", "Attempting truncated decrypt..."); - byte[] truncated = new byte[ciphertextPduBytes.length - 1]; - System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length); - ciphertext = transportDetails.getDecodedMessage(truncated); - plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext); - } else { - throw ime; - } + try { + plaintextPduBytes = sessionCipher.decrypt(decodedCiphertext); + } catch (InvalidMessageException ime) { + // XXX - For some reason, Sprint seems to append a single character to the + // end of message text segments. I don't know why, so here we just try + // truncating the message by one if the MAC fails. + if (ciphertextPduBytes.length > 2) { + Log.w("DecryptingQueue", "Attempting truncated decrypt..."); + byte[] truncated = Util.trim(ciphertextPduBytes, ciphertextPduBytes.length - 1); + decodedCiphertext = transportDetails.getDecodedMessage(truncated); + plaintextPduBytes = sessionCipher.decrypt(decodedCiphertext); + } else { + throw ime; } } @@ -352,36 +342,33 @@ public class DecryptingQueue { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); String plaintextBody; - synchronized (SessionCipher.CIPHER_LOCK) { - try { - Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false); - Recipient recipient = recipients.getPrimaryRecipient(); + try { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, originator, false); + Recipient recipient = recipients.getPrimaryRecipient(); - if (!KeyUtil.isSessionFor(context, recipient)) { - database.markAsNoSession(messageId); - return; - } - - SmsTransportDetails transportDetails = new SmsTransportDetails(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); - byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes()); - byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext); - - plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext)); - } catch (InvalidMessageException e) { - Log.w("DecryptionQueue", e); - database.markAsDecryptFailed(messageId); - return; - } catch (RecipientFormattingException e) { - Log.w("DecryptionQueue", e); - database.markAsDecryptFailed(messageId); - return; - } catch (IOException e) { - Log.w("DecryptionQueue", e); - database.markAsDecryptFailed(messageId); + if (!Session.hasSession(context, masterSecret, recipient)) { + database.markAsNoSession(messageId); return; } + + SmsTransportDetails transportDetails = new SmsTransportDetails(); + SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient); + byte[] decodedCiphertext = transportDetails.getDecodedMessage(body.getBytes()); + byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext); + + plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext)); + } catch (InvalidMessageException e) { + Log.w("DecryptionQueue", e); + database.markAsDecryptFailed(messageId); + return; + } catch (RecipientFormattingException e) { + Log.w("DecryptionQueue", e); + database.markAsDecryptFailed(messageId); + return; + } catch (IOException e) { + Log.w("DecryptionQueue", e); + database.markAsDecryptFailed(messageId); + return; } database.updateMessageBody(masterSecret, messageId, plaintextBody); @@ -414,22 +401,25 @@ public class DecryptingQueue { private void handleKeyExchangeProcessing(String plaintxtBody) { if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { try { - Recipient recipient = new Recipient(null, originator, null, null); - KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(plaintxtBody); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); + Recipient recipient = new Recipient(null, originator, null, null); + KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody); + KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, message); - Log.w("DecryptingQuue", "KeyExchange with fingerprint: " + keyExchangeMessage.getPublicKey().getFingerprint()); - - if (processor.isStale(keyExchangeMessage)) { + if (processor.isStale(message)) { DatabaseFactory.getEncryptingSmsDatabase(context).markAsStaleKeyExchange(messageId); - } else if (processor.isTrusted(keyExchangeMessage)) { + } else if (processor.isTrusted(message)) { DatabaseFactory.getEncryptingSmsDatabase(context).markAsProcessedKeyExchange(messageId); - processor.processKeyExchangeMessage(keyExchangeMessage, threadId); + processor.processKeyExchangeMessage(message, threadId); } } catch (InvalidVersionException e) { Log.w("DecryptingQueue", e); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsInvalidVersionKeyExchange(messageId); } catch (InvalidKeyException e) { Log.w("DecryptingQueue", e); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId); + } catch (InvalidMessageException e) { + Log.w("DecryptingQueue", e); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsCorruptKeyExchange(messageId); } } } diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index 3bd6d57138..d7bf6da1eb 100644 --- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -175,7 +175,7 @@ public class IdentityKeyUtil { byte[] messageBytes = new byte[1 + PublicKey.KEY_SIZE]; System.arraycopy(keyExchangeBytes, 0, messageBytes, 0, messageBytes.length); - byte[] publicKeyBytes = new byte[IdentityKey.SIZE]; + byte[] publicKeyBytes = new byte[IdentityKey.NIST_SIZE]; System.arraycopy(keyExchangeBytes, messageBytes.length, publicKeyBytes, 0, publicKeyBytes.length); int signatureLength = Conversions.byteArrayToShort(keyExchangeBytes, messageBytes.length + publicKeyBytes.length); diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java index 05e0027301..e0d06bba1a 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeInitiator.java @@ -20,17 +20,21 @@ package org.thoughtcrime.securesms.crypto; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.util.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; -import org.whispersystems.textsecure.crypto.KeyUtil; +import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.storage.LocalKeyRecord; +import org.whispersystems.textsecure.storage.SessionRecordV2; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; public class KeyExchangeInitiator { @@ -54,18 +58,42 @@ public class KeyExchangeInitiator { } private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) { - LocalKeyRecord record = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.CURVE25519_INTRODUCED_VERSION); - KeyExchangeMessage message = new KeyExchangeMessage(context, masterSecret, CiphertextMessage.CURVE25519_INTRODUCED_VERSION, record, 0); + int sequence = getRandomSequence(); + int flags = KeyExchangeMessageV2.INITIATE_FLAG; + ECKeyPair baseKey = Curve.generateKeyPairForSession(CiphertextMessage.CURRENT_VERSION); + ECKeyPair ephemeralKey = Curve.generateKeyPairForSession(CiphertextMessage.CURRENT_VERSION); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); + + KeyExchangeMessageV2 message = new KeyExchangeMessageV2(sequence, flags, + baseKey.getPublicKey(), + ephemeralKey.getPublicKey(), + identityKey.getPublicKey()); + OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, message.serialize()); - Log.w("SendKeyActivity", "Sending public key: " + record.getCurrentKeyPair().getPublicKey().getFingerprint()); + SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipient); + sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey); + sessionRecordV2.save(); MessageSender.send(context, masterSecret, textMessage, -1); } - private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret, Recipient recipient) { + private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret, + Recipient recipient) + { return - LocalKeyRecord.hasRecord(context, recipient) && - new LocalKeyRecord(context, masterSecret, recipient).getCurrentKeyPair() != null; + new SessionRecordV2(context, masterSecret, recipient) + .hasPendingKeyExchange(); + } + + private static int getRandomSequence() { + try { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + int candidate = Math.abs(random.nextInt()); + + return candidate % 65535; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } } } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index 30595bf3c4..998ab1ce99 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -18,200 +18,25 @@ package org.thoughtcrime.securesms.crypto; import android.content.Context; -import android.content.Intent; -import android.util.Log; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; -import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; -import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.crypto.InvalidKeyException; -import org.whispersystems.textsecure.crypto.KeyPair; -import org.whispersystems.textsecure.crypto.KeyUtil; +import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; -import org.whispersystems.textsecure.push.PreKeyEntity; -import org.whispersystems.textsecure.storage.InvalidKeyIdException; -import org.whispersystems.textsecure.storage.LocalKeyRecord; -import org.whispersystems.textsecure.storage.PreKeyRecord; -import org.whispersystems.textsecure.storage.RemoteKeyRecord; -import org.whispersystems.textsecure.storage.SessionRecord; -import org.whispersystems.textsecure.util.Conversions; -import org.whispersystems.textsecure.util.Medium; -/** - * This class processes key exchange interactions. - * - * @author Moxie Marlinspike - */ - -public class KeyExchangeProcessor { +public abstract class KeyExchangeProcessor { public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE"; - private Context context; - private Recipient recipient; - private MasterSecret masterSecret; - private LocalKeyRecord localKeyRecord; - private RemoteKeyRecord remoteKeyRecord; - private SessionRecord sessionRecord; + public abstract boolean isStale(KeyExchangeMessage message); + public abstract boolean isTrusted(KeyExchangeMessage message); + public abstract void processKeyExchangeMessage(KeyExchangeMessage message, long threadid) + throws InvalidMessageException; - public KeyExchangeProcessor(Context context, MasterSecret masterSecret, Recipient recipient) { - this.context = context; - this.recipient = recipient; - this.masterSecret = masterSecret; - - this.remoteKeyRecord = new RemoteKeyRecord(context, recipient); - this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient); - this.sessionRecord = new SessionRecord(context, masterSecret, recipient); - } - - public boolean isTrusted(KeyExchangeMessage message) { - return message.hasIdentityKey() && isTrusted(message.getIdentityKey()); - } - - public boolean isTrusted(PreKeyBundleMessage message) { - return isTrusted(message.getIdentityKey()); - } - - public boolean isTrusted(IdentityKey identityKey) { - return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient, - identityKey); - } - - public boolean hasInitiatedSession() { - return localKeyRecord.getCurrentKeyPair() != null; - } - - private boolean needsResponseFromUs() { - return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null; - } - - public boolean isStale(KeyExchangeMessage message) { - int responseKeyId = Conversions.highBitsToMedium(message.getPublicKey().getId()); - - Log.w("KeyExchangeProcessor", "Key Exchange High ID Bits: " + responseKeyId); - - return responseKeyId != 0 && - (localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId); - } - - public void processKeyExchangeMessage(PreKeyBundleMessage message) - throws InvalidKeyIdException, InvalidKeyException + public static KeyExchangeProcessor createFor(Context context, MasterSecret masterSecret, + Recipient recipient, KeyExchangeMessage message) { - int preKeyId = message.getPreKeyId(); - PublicKey remoteKey = message.getPublicKey(); - IdentityKey remoteIdentity = message.getIdentityKey(); - - Log.w("KeyExchangeProcessor", "Received pre-key with remote key ID: " + remoteKey.getId()); - Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId); - - if (!PreKeyRecord.hasRecord(context, preKeyId) && KeyUtil.isSessionFor(context, recipient)) { - Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through..."); - return; - } - - if (!PreKeyRecord.hasRecord(context, preKeyId)) - throw new InvalidKeyIdException("No such prekey: " + preKeyId); - - PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId); - KeyPair preKeyPair = new KeyPair(preKeyId, preKeyRecord.getKeyPair().getKeyPair(), masterSecret); - - localKeyRecord.setCurrentKeyPair(preKeyPair); - localKeyRecord.setNextKeyPair(preKeyPair); - - remoteKeyRecord.setCurrentRemoteKey(remoteKey); - remoteKeyRecord.setLastRemoteKey(remoteKey); - - sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), - remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); - sessionRecord.setIdentityKey(remoteIdentity); - sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), PreKeyBundleMessage.SUPPORTED_VERSION)); - sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion()); - - localKeyRecord.save(); - remoteKeyRecord.save(); - sessionRecord.save(); - - if (preKeyId != Medium.MAX_VALUE) { - PreKeyRecord.delete(context, preKeyId); - } - - DatabaseFactory.getIdentityDatabase(context) - .saveIdentity(masterSecret, recipient, remoteIdentity); + if (message.isLegacy()) return new KeyExchangeProcessorV1(context, masterSecret, recipient); + else return new KeyExchangeProcessorV2(context, masterSecret, recipient); } - - public void processKeyExchangeMessage(PreKeyEntity message, long threadId) { - PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey()); - remoteKeyRecord.setCurrentRemoteKey(remoteKey); - remoteKeyRecord.setLastRemoteKey(remoteKey); - remoteKeyRecord.save(); - - localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, CiphertextMessage.SUPPORTED_VERSION); - localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair()); - localKeyRecord.save(); - - sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), - remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); - sessionRecord.setIdentityKey(message.getIdentityKey()); - sessionRecord.setNegotiatedSessionVersion(CiphertextMessage.SUPPORTED_VERSION); - sessionRecord.setSessionVersion(CiphertextMessage.SUPPORTED_VERSION); - sessionRecord.setPrekeyBundleRequired(true); - sessionRecord.save(); - - DatabaseFactory.getIdentityDatabase(context) - .saveIdentity(masterSecret, recipient, message.getIdentityKey()); - - broadcastSecurityUpdateEvent(context, threadId); - } - - public void processKeyExchangeMessage(KeyExchangeMessage message, long threadId) { - int initiateKeyId = Conversions.lowBitsToMedium(message.getPublicKey().getId()); - message.getPublicKey().setId(initiateKeyId); - - if (needsResponseFromUs()) { - localKeyRecord = KeyUtil.initializeRecordFor(context, masterSecret, recipient, message.getMessageVersion()); - KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId); - OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize()); - Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint()); - Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint()); - MessageSender.send(context, masterSecret, textMessage, threadId); - } - - remoteKeyRecord.setCurrentRemoteKey(message.getPublicKey()); - remoteKeyRecord.setLastRemoteKey(message.getPublicKey()); - remoteKeyRecord.save(); - - sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), - remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); - sessionRecord.setIdentityKey(message.getIdentityKey()); - sessionRecord.setSessionVersion(Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion())); - sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion()); - - Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion())); - - sessionRecord.save(); - - if (message.hasIdentityKey()) { - DatabaseFactory.getIdentityDatabase(context) - .saveIdentity(masterSecret, recipient, message.getIdentityKey()); - } - - DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient); - - broadcastSecurityUpdateEvent(context, threadId); - } - - private static void broadcastSecurityUpdateEvent(Context context, long threadId) { - Intent intent = new Intent(SECURITY_UPDATE_EVENT); - intent.putExtra("thread_id", threadId); - intent.setPackage(context.getPackageName()); - context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); - } - -} +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java new file mode 100644 index 0000000000..d31bdc397d --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV1.java @@ -0,0 +1,154 @@ +package org.thoughtcrime.securesms.crypto; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV1; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.KeyPair; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; +import org.whispersystems.textsecure.storage.LocalKeyRecord; +import org.whispersystems.textsecure.storage.RemoteKeyRecord; +import org.whispersystems.textsecure.storage.SessionRecordV1; +import org.whispersystems.textsecure.util.Conversions; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +/** + * This class processes key exchange interactions. + * + * @author Moxie Marlinspike + */ + +public class KeyExchangeProcessorV1 extends KeyExchangeProcessor { + + private Context context; + private Recipient recipient; + private MasterSecret masterSecret; + private LocalKeyRecord localKeyRecord; + private RemoteKeyRecord remoteKeyRecord; + private SessionRecordV1 sessionRecord; + + public KeyExchangeProcessorV1(Context context, MasterSecret masterSecret, Recipient recipient) { + this.context = context; + this.recipient = recipient; + this.masterSecret = masterSecret; + + this.remoteKeyRecord = new RemoteKeyRecord(context, recipient); + this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient); + this.sessionRecord = new SessionRecordV1(context, masterSecret, recipient); + } + + @Override + public boolean isTrusted(KeyExchangeMessage message) { + return message.hasIdentityKey() && isTrusted(message.getIdentityKey()); + } + + public boolean isTrusted(IdentityKey identityKey) { + return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient, + identityKey); + } + + public boolean hasInitiatedSession() { + return localKeyRecord.getCurrentKeyPair() != null; + } + + private boolean needsResponseFromUs() { + return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null; + } + + @Override + public boolean isStale(KeyExchangeMessage _message) { + KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message; + int responseKeyId = Conversions.highBitsToMedium(message.getRemoteKey().getId()); + + Log.w("KeyExchangeProcessor", "Key Exchange High ID Bits: " + responseKeyId); + + return responseKeyId != 0 && + (localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId); + } + + @Override + public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId) { + KeyExchangeMessageV1 message = (KeyExchangeMessageV1)_message; + int initiateKeyId = Conversions.lowBitsToMedium(message.getRemoteKey().getId()); + message.getRemoteKey().setId(initiateKeyId); + + if (needsResponseFromUs()) { + localKeyRecord = initializeRecordFor(context, masterSecret, recipient); + + KeyExchangeMessageV1 ourMessage = new KeyExchangeMessageV1(context, masterSecret, + Math.min(CiphertextMessage.LEGACY_VERSION, + message.getMaxVersion()), + localKeyRecord, initiateKeyId); + + OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize()); + Log.w("KeyExchangeProcessorV1", "Responding with key exchange message fingerprint: " + ourMessage.getRemoteKey().getFingerprint()); + Log.w("KeyExchangeProcessorV1", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint()); + MessageSender.send(context, masterSecret, textMessage, threadId); + } + + remoteKeyRecord.setCurrentRemoteKey(message.getRemoteKey()); + remoteKeyRecord.setLastRemoteKey(message.getRemoteKey()); + remoteKeyRecord.save(); + + sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), + remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); + sessionRecord.setIdentityKey(message.getIdentityKey()); + sessionRecord.setSessionVersion(Math.min(1, message.getMaxVersion())); + + Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(1, message.getMaxVersion())); + + sessionRecord.save(); + + if (message.hasIdentityKey()) { + DatabaseFactory.getIdentityDatabase(context) + .saveIdentity(masterSecret, recipient, message.getIdentityKey()); + } + + DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient); + + broadcastSecurityUpdateEvent(context, threadId); + } + + private static void broadcastSecurityUpdateEvent(Context context, long threadId) { + Intent intent = new Intent(SECURITY_UPDATE_EVENT); + intent.putExtra("thread_id", threadId); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); + } + + public LocalKeyRecord initializeRecordFor(Context context, + MasterSecret masterSecret, + Recipient recipient) + { + Log.w("KeyExchangeProcessorV1", "Initializing local key pairs..."); + try { + SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); + int initialId = secureRandom.nextInt(4094) + 1; + + KeyPair currentPair = new KeyPair(initialId, Curve.generateKeyPairForSession(1), masterSecret); + KeyPair nextPair = new KeyPair(initialId + 1, Curve.generateKeyPairForSession(1), masterSecret); + LocalKeyRecord record = new LocalKeyRecord(context, masterSecret, recipient); + + record.setCurrentKeyPair(currentPair); + record.setNextKeyPair(nextPair); + record.save(); + + return record; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java new file mode 100644 index 0000000000..8d4326699e --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessorV2.java @@ -0,0 +1,221 @@ +package org.thoughtcrime.securesms.crypto; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; +import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessageV2; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.IdentityKeyPair; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECKeyPair; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; +import org.whispersystems.textsecure.crypto.ratchet.RatchetingSession; +import org.whispersystems.textsecure.push.PreKeyEntity; +import org.whispersystems.textsecure.storage.InvalidKeyIdException; +import org.whispersystems.textsecure.storage.PreKeyRecord; +import org.whispersystems.textsecure.storage.Session; +import org.whispersystems.textsecure.storage.SessionRecordV2; +import org.whispersystems.textsecure.util.Medium; + +/** + * This class processes key exchange interactions. + * + * @author Moxie Marlinspike + */ + +public class KeyExchangeProcessorV2 extends KeyExchangeProcessor { + + private Context context; + private Recipient recipient; + private MasterSecret masterSecret; + private SessionRecordV2 sessionRecord; + + public KeyExchangeProcessorV2(Context context, MasterSecret masterSecret, Recipient recipient) { + this.context = context; + this.recipient = recipient; + this.masterSecret = masterSecret; + this.sessionRecord = new SessionRecordV2(context, masterSecret, recipient); + } + + public boolean isTrusted(PreKeyWhisperMessage message) { + return isTrusted(message.getIdentityKey()); + } + + public boolean isTrusted(KeyExchangeMessage message) { + return message.hasIdentityKey() && isTrusted(message.getIdentityKey()); + } + + public boolean isTrusted(IdentityKey identityKey) { + return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient, + identityKey); + } + + public boolean isStale(KeyExchangeMessage m) { + KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m; + return + message.isResponse() && + (!sessionRecord.hasPendingKeyExchange() || + sessionRecord.getPendingKeyExchangeSequence() != message.getSequence()) && + !message.isResponseForSimultaneousInitiate(); + } + + public void processKeyExchangeMessage(PreKeyWhisperMessage message) + throws InvalidKeyIdException, InvalidKeyException + { + int preKeyId = message.getPreKeyId(); + ECPublicKey theirBaseKey = message.getBaseKey(); + ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral(); + IdentityKey theirIdentityKey = message.getIdentityKey(); + + Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId); + + if (!PreKeyRecord.hasRecord(context, preKeyId) && Session.hasSession(context, masterSecret, recipient)) { + Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through..."); + return; + } + + if (!PreKeyRecord.hasRecord(context, preKeyId)) + throw new InvalidKeyIdException("No such prekey: " + preKeyId); + + PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId); + ECKeyPair ourBaseKey = preKeyRecord.getKeyPair().getKeyPair(); + ECKeyPair ourEphemeralKey = ourBaseKey; + IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType()); + + sessionRecord.clear(); + + RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey, + theirEphemeralKey, ourIdentityKey, theirIdentityKey); + + sessionRecord.save(); + + if (preKeyId != Medium.MAX_VALUE) { + PreKeyRecord.delete(context, preKeyId); + } + + DatabaseFactory.getIdentityDatabase(context) + .saveIdentity(masterSecret, recipient, theirIdentityKey); + } + + public void processKeyExchangeMessage(PreKeyEntity message, long threadId) + throws InvalidKeyException + { + ECKeyPair ourBaseKey = Curve.generateKeyPairForSession(2); + ECKeyPair ourEphemeralKey = Curve.generateKeyPairForSession(2); + ECPublicKey theirBaseKey = message.getPublicKey().getPublicKey(); + ECPublicKey theirEphemeralKey = theirBaseKey; + IdentityKey theirIdentityKey = message.getIdentityKey(); + IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, + ourBaseKey.getPublicKey() + .getType()); + + sessionRecord.clear(); + + RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey, + theirEphemeralKey, ourIdentityKey, theirIdentityKey); + + sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey()); + sessionRecord.save(); + + DatabaseFactory.getIdentityDatabase(context) + .saveIdentity(masterSecret, recipient, message.getIdentityKey()); + + broadcastSecurityUpdateEvent(context, threadId); + } + + @Override + public void processKeyExchangeMessage(KeyExchangeMessage _message, long threadId) + throws InvalidMessageException + { + try { + KeyExchangeMessageV2 message = (KeyExchangeMessageV2)_message; + + Log.w("KeyExchangeProcessorV2", "Received key exchange with sequence: " + message.getSequence()); + + if (message.isInitiate()) { + ECKeyPair ourBaseKey, ourEphemeralKey; + IdentityKeyPair ourIdentityKey; + + int flags = KeyExchangeMessageV2.RESPONSE_FLAG; + + Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate."); + + if (!sessionRecord.hasPendingKeyExchange()) { + Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate..."); + ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType()); + ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType()); + ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType()); + + sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey, + ourIdentityKey); + } else { + Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate..."); + ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey(); + ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey(); + ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey(); + flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG; + + sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey, + ourIdentityKey); + } + + KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(), + flags, ourBaseKey.getPublicKey(), + ourEphemeralKey.getPublicKey(), + ourIdentityKey.getPublicKey()); + + OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, + ourMessage.serialize()); + MessageSender.send(context, masterSecret, textMessage, threadId); + } + + if (message.getSequence() != sessionRecord.getPendingKeyExchangeSequence()) { + Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " + + "Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate()); + return; + } + + ECKeyPair ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey(); + ECKeyPair ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey(); + IdentityKeyPair ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey(); + + sessionRecord.clear(); + + RatchetingSession.initializeSession(sessionRecord, ourBaseKey, message.getBaseKey(), + ourEphemeralKey, message.getEphemeralKey(), + ourIdentityKey, message.getIdentityKey()); + + sessionRecord.setSessionVersion(message.getVersion()); + Session.clearV1SessionFor(context, recipient); + sessionRecord.save(); + + DatabaseFactory.getIdentityDatabase(context) + .saveIdentity(masterSecret, recipient, message.getIdentityKey()); + + DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient); + + broadcastSecurityUpdateEvent(context, threadId); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } + } + + private static void broadcastSecurityUpdateEvent(Context context, long threadId) { + Intent intent = new Intent(KeyExchangeProcessorV1.SECURITY_UPDATE_EVENT); + intent.putExtra("thread_id", threadId); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); + } + +} diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java index 2e02c07a81..00f32b1083 100644 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java @@ -17,151 +17,39 @@ */ package org.thoughtcrime.securesms.crypto.protocol; -import android.content.Context; -import android.util.Log; - -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.PublicKey; -import org.whispersystems.textsecure.crypto.ecc.Curve; import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; -import org.whispersystems.textsecure.util.Util; import java.io.IOException; -/** - * A class for constructing and parsing key exchange messages. - * - * A key exchange message is basically represented by the following format: - * - * 1) 4 bits - * 2) 4 bits - * 3) A serialized public key - * 4) (Optional) An identity key. - * 5) (if #4) A signature over the identity key, version bits, and serialized public key. - * - * A serialized public key is basically represented by the following format: - * - * 1) A 3 byte key ID. - * 2) An ECC key encoded with point compression. - * - * An initiating key ID is initialized with the bottom 12 bits of the ID set. A responding key - * ID does the same, but puts the initiating key ID's bottom 12 bits in the top 12 bits of its - * ID. This is used to correlate key exchange responses. - * - * @author Moxie Marlinspike - * - */ -public class KeyExchangeMessage { +public abstract class KeyExchangeMessage { + public abstract boolean isLegacy(); + public abstract IdentityKey getIdentityKey(); + public abstract boolean hasIdentityKey(); + public abstract int getMaxVersion(); + public abstract int getVersion(); - private final int messageVersion; - private final int supportedVersion; - private final PublicKey publicKey; - private final String serialized; - private IdentityKey identityKey; - - public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) { - this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); - this.messageVersion = messageVersion; - this.supportedVersion = CiphertextMessage.SUPPORTED_VERSION; - - publicKey.setId(publicKey.getId() | (highIdBits << 12)); - - byte[] versionBytes = {Conversions.intsToByteHighAndLow(messageVersion, supportedVersion)}; - byte[] publicKeyBytes = publicKey.serialize(); - - byte[] serializedBytes; - - if (includeIdentityNoSignature(messageVersion, context)) { - byte[] identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE).serialize(); - - serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey); - } else if (includeIdentitySignature(messageVersion, context)) { - byte[] prolog = Util.combine(versionBytes, publicKeyBytes); - - serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog); - } else { - serializedBytes = Util.combine(versionBytes, publicKeyBytes); - } - - if (messageVersion < 1) this.serialized = Base64.encodeBytes(serializedBytes); - else this.serialized = Base64.encodeBytesWithoutPadding(serializedBytes); - } - - public KeyExchangeMessage(String messageBody) throws InvalidVersionException, InvalidKeyException { + public static KeyExchangeMessage createFor(String rawMessage) + throws InvalidMessageException, InvalidKeyException, InvalidVersionException + { try { - byte[] keyBytes = Base64.decode(messageBody); - this.messageVersion = Conversions.highBitsToInt(keyBytes[0]); - this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]); - this.serialized = messageBody; - - if (messageVersion > CiphertextMessage.SUPPORTED_VERSION) - throw new InvalidVersionException("Key exchange with version: " + messageVersion); + byte[] decodedMessage = Base64.decodeWithoutPadding(rawMessage); - if (messageVersion >= 1) - keyBytes = Base64.decodeWithoutPadding(messageBody); - - this.publicKey = new PublicKey(keyBytes, 1); - - if (keyBytes.length <= PublicKey.KEY_SIZE + 1) { - this.identityKey = null; - } else if (messageVersion == 1) { - try { - this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes); - } catch (InvalidKeyException ike) { - Log.w("KeyUtil", ike); - this.identityKey = null; - } - } else if (messageVersion == 2) { - try { - this.identityKey = new IdentityKey(keyBytes, 1 + PublicKey.KEY_SIZE); - } catch (InvalidKeyException ike) { - Log.w("KeyUtil", ike); - this.identityKey = null; - } + if (Conversions.highBitsToInt(decodedMessage[0]) <= CiphertextMessage.LEGACY_VERSION) { + return new KeyExchangeMessageV1(rawMessage); + } else { + return new KeyExchangeMessageV2(rawMessage); } - } catch (IOException ioe) { - throw new InvalidKeyException(ioe); + } catch (IOException e) { + throw new InvalidMessageException(e); } } - - private static boolean includeIdentitySignature(int messageVersion, Context context) { - return IdentityKeyUtil.hasIdentityKey(context, Curve.NIST_TYPE) && (messageVersion == 1); - } - - private static boolean includeIdentityNoSignature(int messageVersion, Context context) { - return IdentityKeyUtil.hasIdentityKey(context, Curve.DJB_TYPE) && (messageVersion >= 2); - } - - public PublicKey getPublicKey() { - return publicKey; - } - - public IdentityKey getIdentityKey() { - return identityKey; - } - - public int getMaxVersion() { - return supportedVersion; - } - - public int getMessageVersion() { - return messageVersion; - } - - public boolean hasIdentityKey() { - return identityKey != null; - } - - public String serialize() { - return serialized; - } -} +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV1.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV1.java new file mode 100644 index 0000000000..191c5e6992 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV1.java @@ -0,0 +1,160 @@ +package org.thoughtcrime.securesms.crypto.protocol; + +import android.content.Context; +import android.util.Log; + +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; +import org.whispersystems.textsecure.storage.LocalKeyRecord; +import org.whispersystems.textsecure.util.Base64; +import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Util; + +import java.io.IOException; + +/** + * A class for constructing and parsing key exchange messages. + * + * A key exchange message is basically represented by the following format: + * + * 1) 4 bits + * 2) 4 bits + * 3) A serialized public key + * 4) (Optional) An identity key. + * 5) (if #4) A signature over the identity key, version bits, and serialized public key. + * + * A serialized public key is basically represented by the following format: + * + * 1) A 3 byte key ID. + * 2) An ECC key encoded with point compression. + * + * An initiating key ID is initialized with the bottom 12 bits of the ID set. A responding key + * ID does the same, but puts the initiating key ID's bottom 12 bits in the top 12 bits of its + * ID. This is used to correlate key exchange responses. + * + * @author Moxie Marlinspike + * + */ + +public class KeyExchangeMessageV1 extends KeyExchangeMessage { + + private final int messageVersion; + private final int supportedVersion; + private final PublicKey publicKey; + private final String serialized; + private IdentityKey identityKey; + + public KeyExchangeMessageV1(Context context, MasterSecret masterSecret, + int messageVersion, LocalKeyRecord record, int highIdBits) + { + this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); + this.messageVersion = messageVersion; + this.supportedVersion = CiphertextMessage.CURRENT_VERSION; + + publicKey.setId(publicKey.getId() | (highIdBits << 12)); + + byte[] versionBytes = {Conversions.intsToByteHighAndLow(messageVersion, supportedVersion)}; + byte[] publicKeyBytes = publicKey.serialize(); + + byte[] serializedBytes; + + if (includeIdentityNoSignature(messageVersion, context)) { + byte[] identityKey = IdentityKeyUtil.getIdentityKey(context, Curve.DJB_TYPE).serialize(); + + serializedBytes = Util.combine(versionBytes, publicKeyBytes, identityKey); + } else if (includeIdentitySignature(messageVersion, context)) { + byte[] prolog = Util.combine(versionBytes, publicKeyBytes); + + serializedBytes = IdentityKeyUtil.getSignedKeyExchange(context, masterSecret, prolog); + } else { + serializedBytes = Util.combine(versionBytes, publicKeyBytes); + } + + if (messageVersion < 1) this.serialized = Base64.encodeBytes(serializedBytes); + else this.serialized = Base64.encodeBytesWithoutPadding(serializedBytes); + } + + public KeyExchangeMessageV1(String messageBody) throws InvalidVersionException, InvalidKeyException { + try { + byte[] keyBytes = Base64.decode(messageBody); + this.messageVersion = Conversions.highBitsToInt(keyBytes[0]); + this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]); + this.serialized = messageBody; + + if (messageVersion > 1) + throw new InvalidVersionException("Legacy key exchange with version: " + messageVersion); + + if (messageVersion >= 1) + keyBytes = Base64.decodeWithoutPadding(messageBody); + + this.publicKey = new PublicKey(keyBytes, 1); + + if (keyBytes.length <= PublicKey.KEY_SIZE + 1) { + this.identityKey = null; + } else if (messageVersion == 1) { + try { + this.identityKey = IdentityKeyUtil.verifySignedKeyExchange(keyBytes); + } catch (InvalidKeyException ike) { + Log.w("KeyUtil", ike); + this.identityKey = null; + } + } else if (messageVersion == 2) { + try { + this.identityKey = new IdentityKey(keyBytes, 1 + PublicKey.KEY_SIZE); + } catch (InvalidKeyException ike) { + Log.w("KeyUtil", ike); + this.identityKey = null; + } + } + } catch (IOException ioe) { + throw new InvalidKeyException(ioe); + } + } + + private static boolean includeIdentitySignature(int messageVersion, Context context) { + return IdentityKeyUtil.hasIdentityKey(context, Curve.NIST_TYPE) && (messageVersion == 1); + } + + private static boolean includeIdentityNoSignature(int messageVersion, Context context) { + return IdentityKeyUtil.hasIdentityKey(context, Curve.DJB_TYPE) && (messageVersion >= 2); + } + + @Override + public boolean isLegacy() { + return true; + } + + @Override + public IdentityKey getIdentityKey() { + return identityKey; + } + + public PublicKey getRemoteKey() { + return publicKey; + } + + @Override + public int getMaxVersion() { + return supportedVersion; + } + + @Override + public int getVersion() { + return messageVersion; + } + + @Override + public boolean hasIdentityKey() { + return identityKey != null; + } + + public String serialize() { + return serialized; + } +} diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java new file mode 100644 index 0000000000..6b64d340b3 --- /dev/null +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessageV2.java @@ -0,0 +1,152 @@ +package org.thoughtcrime.securesms.crypto.protocol; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; +import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.ecc.ECPublicKey; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; +import org.whispersystems.textsecure.crypto.protocol.WhisperProtos; +import org.whispersystems.textsecure.util.Base64; +import org.whispersystems.textsecure.util.Conversions; +import org.whispersystems.textsecure.util.Util; + +import java.io.IOException; + +public class KeyExchangeMessageV2 extends KeyExchangeMessage { + + public static final int INITIATE_FLAG = 0x01; + public static final int RESPONSE_FLAG = 0X02; + public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04; + + private final int version; + private final int supportedVersion; + private final int sequence; + private final int flags; + + private final ECPublicKey baseKey; + private final ECPublicKey ephemeralKey; + private final IdentityKey identityKey; + private final byte[] serialized; + + public KeyExchangeMessageV2(int sequence, int flags, + ECPublicKey baseKey, ECPublicKey ephemeralKey, + IdentityKey identityKey) + { + this.supportedVersion = CiphertextMessage.CURRENT_VERSION; + this.version = CiphertextMessage.CURRENT_VERSION; + this.sequence = sequence; + this.flags = flags; + this.baseKey = baseKey; + this.ephemeralKey = ephemeralKey; + this.identityKey = identityKey; + + byte[] version = {Conversions.intsToByteHighAndLow(this.version, this.supportedVersion)}; + byte[] message = WhisperProtos.KeyExchangeMessage.newBuilder() + .setId((sequence << 5) | flags) + .setBaseKey(ByteString.copyFrom(baseKey.serialize())) + .setEphemeralKey(ByteString.copyFrom(ephemeralKey.serialize())) + .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) + .build().toByteArray(); + + this.serialized = Util.combine(version, message); + } + + public KeyExchangeMessageV2(String serializedAndEncoded) + throws InvalidMessageException, InvalidVersionException + { + try { + byte[] serialized = Base64.decodeWithoutPadding(serializedAndEncoded); + byte[][] parts = Util.split(serialized, 1, serialized.length - 1); + + this.version = Conversions.highBitsToInt(parts[0][0]); + this.supportedVersion = Conversions.lowBitsToInt(parts[0][0]); + + if (this.version > CiphertextMessage.CURRENT_VERSION) { + throw new InvalidVersionException("Unknown version: " + this.version); + } + + WhisperProtos.KeyExchangeMessage message = WhisperProtos.KeyExchangeMessage.parseFrom(parts[1]); + + if (!message.hasId() || !message.hasBaseKey() || + !message.hasEphemeralKey() || !message.hasIdentityKey()) + { + throw new InvalidMessageException("Some required fields missing!"); + } + + this.sequence = message.getId() >> 5; + this.flags = message.getId() & 0x1f; + this.serialized = serialized; + this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0); + this.ephemeralKey = Curve.decodePoint(message.getEphemeralKey().toByteArray(), 0); + this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0); + } catch (InvalidProtocolBufferException e) { + throw new InvalidMessageException(e); + } catch (InvalidKeyException e) { + throw new InvalidMessageException(e); + } catch (IOException e) { + throw new InvalidMessageException(e); + } + } + + @Override + public int getVersion() { + return version; + } + + public ECPublicKey getBaseKey() { + return baseKey; + } + + public ECPublicKey getEphemeralKey() { + return ephemeralKey; + } + + @Override + public boolean isLegacy() { + return false; + } + + @Override + public IdentityKey getIdentityKey() { + return identityKey; + } + + @Override + public boolean hasIdentityKey() { + return true; + } + + @Override + public int getMaxVersion() { + return supportedVersion; + } + + public boolean isResponse() { + return ((flags & RESPONSE_FLAG) != 0); + } + + public boolean isInitiate() { + return (flags & INITIATE_FLAG) != 0; + } + + public boolean isResponseForSimultaneousInitiate() { + return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0; + } + + public int getFlags() { + return flags; + } + + public int getSequence() { + return sequence; + } + + public String serialize() { + return Base64.encodeBytesWithoutPadding(serialized); + } +} diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java index f88ec7ca65..2c694c32ad 100644 --- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -30,8 +30,8 @@ import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.storage.SessionRecord; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; @@ -392,8 +392,7 @@ public class DatabaseFactory { if (name.matches("[0-9]+")) { long recipientId = Long.parseLong(name); - SessionRecord sessionRecord = new SessionRecord(context, masterSecret, recipientId); - IdentityKey identityKey = sessionRecord.getIdentityKey(); + IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId); if (identityKey != null) { MasterCipher masterCipher = new MasterCipher(masterSecret); diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index 100eb27a0e..cd71f4b597 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -166,6 +166,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns { updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT); } + public void markAsInvalidVersionKeyExchange(long id) { + updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT); + } + public void markAsSecure(long id) { updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT); } diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java index 5f3b0d95b3..ce923d91e4 100644 --- a/src/org/thoughtcrime/securesms/service/PushReceiver.java +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -8,7 +8,7 @@ import android.util.Pair; import com.google.protobuf.InvalidProtocolBufferException; import org.thoughtcrime.securesms.crypto.DecryptingQueue; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -24,9 +24,10 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.storage.InvalidKeyIdException; @@ -46,9 +47,9 @@ public class PushReceiver { } public void process(MasterSecret masterSecret, Intent intent) { - if (intent.getAction().equals(SendReceiveService.RECEIVE_PUSH_ACTION)) { + if (SendReceiveService.RECEIVE_PUSH_ACTION.equals(intent.getAction())) { handleMessage(masterSecret, intent); - } else if (intent.getAction().equals(SendReceiveService.DECRYPTED_PUSH_ACTION)) { + } else if (SendReceiveService.DECRYPTED_PUSH_ACTION.equals(intent.getAction())) { handleDecrypt(masterSecret, intent); } } @@ -81,25 +82,25 @@ public class PushReceiver { } else { Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); - MessageNotifier.updateNotification(context, masterSecret, threadId); + MessageNotifier.updateNotification(context, null, threadId); } } private void handleReceivedPreKeyBundle(MasterSecret masterSecret, IncomingPushMessage message) { if (masterSecret == null) { - handleReceivedSecureMessage(masterSecret, message); + handleReceivedSecureMessage(null, message); return; } try { - Recipient recipient = new Recipient(null, message.getSource(), null, null); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); - PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(message.getBody()); + Recipient recipient = new Recipient(null, message.getSource(), null, null); + KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient); + PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(message.getBody()); if (processor.isTrusted(preKeyExchange)) { processor.processKeyExchangeMessage(preKeyExchange); - IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage().serialize()); + IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getWhisperMessage().serialize()); handleReceivedSecureMessage(masterSecret, bundledMessage); } else { SmsTransportDetails transportDetails = new SmsTransportDetails(); @@ -110,13 +111,16 @@ public class PushReceiver { DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, textMessage); } } catch (InvalidKeyException e) { - Log.w("SmsReceiver", e); + Log.w("PushReceiver", e); handleReceivedCorruptedKey(masterSecret, message, false); } catch (InvalidVersionException e) { - Log.w("SmsReceiver", e); + Log.w("PushReceiver", e); handleReceivedCorruptedKey(masterSecret, message, true); } catch (InvalidKeyIdException e) { - Log.w("SmsReceiver", e); + Log.w("PushReceiver", e); + handleReceivedCorruptedKey(masterSecret, message, false); + } catch (InvalidMessageException e) { + Log.w("PushReceiver", e); handleReceivedCorruptedKey(masterSecret, message, false); } } @@ -237,9 +241,7 @@ public class PushReceiver { placeholder = new IncomingEncryptedMessage(placeholder, ""); } - Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) - .insertMessageInbox(masterSecret, - placeholder); - return messageAndThreadId; + return DatabaseFactory.getEncryptingSmsDatabase(context) + .insertMessageInbox(masterSecret, placeholder); } } diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index bce7133dec..aaaddb66b4 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -23,6 +23,7 @@ import android.util.Pair; import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -33,15 +34,17 @@ import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; +import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; +import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage; +import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import java.io.IOException; @@ -97,26 +100,27 @@ public class SmsReceiver { } } - private Pair storePreKeyBundledMessage(MasterSecret masterSecret, - IncomingKeyExchangeMessage message) + private Pair storePreKeyWhisperMessage(MasterSecret masterSecret, + IncomingPreKeyBundleMessage message) { Log.w("SmsReceiver", "Processing prekey message..."); try { - Recipient recipient = new Recipient(null, message.getSender(), null, null); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); - SmsTransportDetails transportDetails = new SmsTransportDetails(); - PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(transportDetails.getDecodedMessage(message.getMessageBody().getBytes())); + Recipient recipient = new Recipient(null, message.getSender(), null, null); + KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient); + SmsTransportDetails transportDetails = new SmsTransportDetails(); + byte[] rawMessage = transportDetails.getDecodedMessage(message.getMessageBody().getBytes()); + PreKeyWhisperMessage preKeyExchange = new PreKeyWhisperMessage(rawMessage); if (processor.isTrusted(preKeyExchange)) { processor.processKeyExchangeMessage(preKeyExchange); - CiphertextMessage ciphertextMessage = preKeyExchange.getBundledMessage(); + WhisperMessageV2 ciphertextMessage = preKeyExchange.getWhisperMessage(); String bundledMessageBody = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody); Pair messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage); - Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT); + Intent intent = new Intent(KeyExchangeProcessorV2.SECURITY_UPDATE_EVENT); intent.putExtra("thread_id", messageAndThreadId.second); intent.setPackage(context.getPackageName()); context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION); @@ -135,6 +139,9 @@ public class SmsReceiver { } catch (IOException e) { Log.w("SmsReceive", e); message.setCorrupted(true); + } catch (InvalidMessageException e) { + Log.w("SmsReceiver", e); + message.setCorrupted(true); } return storeStandardMessage(masterSecret, message); @@ -145,19 +152,17 @@ public class SmsReceiver { { if (masterSecret != null && TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) { try { - Recipient recipient = new Recipient(null, message.getSender(), null, null); - KeyExchangeMessage keyExchangeMessage = new KeyExchangeMessage(message.getMessageBody()); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); + Recipient recipient = new Recipient(null, message.getSender(), null, null); + KeyExchangeMessage exchangeMessage = KeyExchangeMessage.createFor(message.getMessageBody()); + KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipient, exchangeMessage); - Log.w("SmsReceiver", "Received key with fingerprint: " + keyExchangeMessage.getPublicKey().getFingerprint()); - - if (processor.isStale(keyExchangeMessage)) { + if (processor.isStale(exchangeMessage)) { message.setStale(true); - } else if (processor.isTrusted(keyExchangeMessage)) { + } else if (processor.isTrusted(exchangeMessage)) { message.setProcessed(true); Pair messageAndThreadId = storeStandardMessage(masterSecret, message); - processor.processKeyExchangeMessage(keyExchangeMessage, messageAndThreadId.second); + processor.processKeyExchangeMessage(exchangeMessage, messageAndThreadId.second); return messageAndThreadId; } @@ -167,6 +172,9 @@ public class SmsReceiver { } catch (InvalidKeyException e) { Log.w("SmsReceiver", e); message.setCorrupted(true); + } catch (InvalidMessageException e) { + Log.w("SmsReceiver", e); + message.setCorrupted(true); } } @@ -175,7 +183,7 @@ public class SmsReceiver { private Pair storeMessage(MasterSecret masterSecret, IncomingTextMessage message) { if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message); - else if (message.isPreKeyBundle()) return storePreKeyBundledMessage(masterSecret, (IncomingKeyExchangeMessage) message); + else if (message.isPreKeyBundle()) return storePreKeyWhisperMessage(masterSecret, (IncomingPreKeyBundleMessage) message); else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message); else return storeStandardMessage(masterSecret, message); } @@ -191,7 +199,7 @@ public class SmsReceiver { } public void process(MasterSecret masterSecret, Intent intent) { - if (intent.getAction().equals(SendReceiveService.RECEIVE_SMS_ACTION)) { + if (SendReceiveService.RECEIVE_SMS_ACTION.equals(intent.getAction())) { handleReceiveMessage(masterSecret, intent); } } diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java index f1154e8167..d0b499949c 100644 --- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java @@ -21,7 +21,6 @@ import android.content.Context; import android.telephony.TelephonyManager; import android.util.Log; -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.mms.MmsRadio; import org.thoughtcrime.securesms.mms.MmsRadioException; @@ -30,10 +29,8 @@ import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.mms.TextTransport; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.recipients.Recipient; -import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; -import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.util.Hex; @@ -158,9 +155,8 @@ public class MmsTransport { private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) { TextTransport transportDetails = new TextTransport(); Recipient recipient = new Recipient(null, recipientString, null, null); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); - CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, pduBytes); + SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient); + CiphertextMessage ciphertextMessage = sessionCipher.encrypt(pduBytes); return transportDetails.getEncodedMessage(ciphertextMessage.serialize()); } diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 8fdb402df9..82a0bf0521 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -24,7 +24,7 @@ import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.Release; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; +import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.recipients.Recipient; @@ -34,14 +34,10 @@ import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.TextSecurePushCredentials; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.textsecure.crypto.AttachmentCipher; -import org.whispersystems.textsecure.crypto.IdentityKey; -import org.whispersystems.textsecure.crypto.IdentityKeyPair; -import org.whispersystems.textsecure.crypto.KeyUtil; +import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; -import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.push.OutgoingPushMessage; import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PushAttachmentData; @@ -51,6 +47,7 @@ import org.whispersystems.textsecure.push.PushDestination; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.RateLimitException; +import org.whispersystems.textsecure.storage.SessionRecordV2; import java.io.IOException; import java.util.LinkedList; @@ -166,62 +163,27 @@ public class PushTransport extends BaseTransport { PushDestination pushDestination, byte[] plaintext) throws IOException { - if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { - Log.w("PushTransport", "Sending standard ciphertext message..."); - byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext); - return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); - } else if (KeyUtil.isSessionFor(context, recipient)) { - Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session..."); - byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext); - return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); + if (!SessionRecordV2.hasSession(context, masterSecret, recipient)) { + try { + PreKeyEntity preKey = socket.getPreKey(pushDestination); + KeyExchangeProcessorV2 processor = new KeyExchangeProcessorV2(context, masterSecret, recipient); + + processor.processKeyExchangeMessage(preKey, threadId); + } catch (InvalidKeyException e) { + Log.w("PushTransport", e); + throw new IOException("Invalid PreKey!"); + } + } + + SessionCipher cipher = SessionCipher.createFor(context, masterSecret, recipient); + CiphertextMessage message = cipher.encrypt(plaintext); + + if (message.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) { + return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, message.serialize()); + } else if (message.getType() == CiphertextMessage.CURRENT_WHISPER_TYPE) { + return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, message.serialize()); } else { - Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session..."); - byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, threadId, recipient, pushDestination, plaintext); - return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); + throw new AssertionError("Unknown ciphertext type: " + message.getType()); } } - - private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, - byte[] plaintext) - { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - IdentityKey identityKey = identityKeyPair.getPublicKey(); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); - CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); - PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey); - - return preKeyBundleMessage.serialize(); - } - - private byte[] getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket, - long threadId, - Recipient recipient, - PushDestination pushDestination, - byte[] plaintext) - throws IOException - { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - IdentityKey identityKey = identityKeyPair.getPublicKey(); - PreKeyEntity preKey = socket.getPreKey(pushDestination); - KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); - - processor.processKeyExchangeMessage(preKey, threadId); - - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); - CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); - PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey); - - return preKeyBundleMessage.serialize(); - } - - private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext) - throws IOException - { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, Curve.DJB_TYPE); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); - CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); - - return ciphertextMessage.serialize(); - } - } diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index 38928059f1..8959fd234d 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -22,7 +22,6 @@ import android.content.Context; import android.telephony.SmsManager; import android.util.Log; -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.SendReceiveService; @@ -32,13 +31,9 @@ import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.textsecure.crypto.IdentityKeyPair; -import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; -import org.whispersystems.textsecure.crypto.ecc.Curve; +import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; -import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import java.util.ArrayList; @@ -69,7 +64,7 @@ public class SmsTransport extends BaseTransport { } ArrayList messages = multipartMessageHandler.divideMessage(transportMessage); - ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, true); + ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure()); ArrayList deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages); Log.w("SmsTransport", "Secure divide into message parts: " + messages.size()); @@ -164,30 +159,20 @@ public class SmsTransport extends BaseTransport { private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret, OutgoingTextMessage message) { - Recipient recipient = message.getRecipients().getPrimaryRecipient(); - String body = message.getMessageBody(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, - Curve.DJB_TYPE); + Recipient recipient = message.getRecipients().getPrimaryRecipient(); + String body = message.getMessageBody(); + SmsTransportDetails transportDetails = new SmsTransportDetails(); + SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipient); + byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes()); + CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext); + String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); - SmsTransportDetails transportDetails = new SmsTransportDetails(); - - if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { - Log.w("SmsTransport", "Delivering standard ciphertext..."); - - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); - byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes()); - CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, paddedPlaintext); - String ciphertxt = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); - - return message.withBody(ciphertxt); + if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) { + message = new OutgoingPrekeyBundleMessage(message, encodedCiphertext); } else { - Log.w("SmsTransport", "Delivering prekeybundle ciphertext..."); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); - CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, body.getBytes()); - PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey.getPublicKey()); - byte[] cipherText = preKeyBundleMessage.serialize(); - - return new OutgoingPrekeyBundleMessage(message, new String(transportDetails.getEncodedMessage(cipherText))); + message = message.withBody(encodedCiphertext); } + + return message; } }