├── settings.gradle ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── protobuf ├── Makefile ├── FingerprintProtocol.proto ├── WhisperTextProtocol.proto └── LocalStorageProtocol.proto ├── android ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── org │ │ │ └── whispersystems │ │ │ └── libsignal │ │ │ └── util │ │ │ └── AndroidSignalProtocolLogger.java │ └── androidTest │ │ └── java │ │ └── org │ │ └── whispersystems │ │ └── libsignal │ │ └── CurveTest.java └── build.gradle ├── java ├── src │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── whispersystems │ │ │ └── libsignal │ │ │ ├── StaleKeyExchangeException.java │ │ │ ├── util │ │ │ ├── Medium.java │ │ │ ├── guava │ │ │ │ ├── Supplier.java │ │ │ │ ├── Function.java │ │ │ │ ├── Absent.java │ │ │ │ └── Present.java │ │ │ ├── Pair.java │ │ │ ├── Hex.java │ │ │ └── KeyHelper.java │ │ │ ├── DecryptionCallback.java │ │ │ ├── kdf │ │ │ ├── HKDFv2.java │ │ │ ├── HKDFv3.java │ │ │ ├── DerivedRootSecrets.java │ │ │ ├── DerivedMessageSecrets.java │ │ │ └── HKDF.java │ │ │ ├── LegacyMessageException.java │ │ │ ├── state │ │ │ ├── SignalProtocolStore.java │ │ │ ├── impl │ │ │ │ ├── InMemoryPreKeyStore.java │ │ │ │ ├── InMemoryIdentityKeyStore.java │ │ │ │ ├── InMemorySignedPreKeyStore.java │ │ │ │ ├── InMemorySessionStore.java │ │ │ │ └── InMemorySignalProtocolStore.java │ │ │ ├── PreKeyStore.java │ │ │ ├── SignedPreKeyStore.java │ │ │ ├── PreKeyRecord.java │ │ │ ├── IdentityKeyStore.java │ │ │ ├── SignedPreKeyRecord.java │ │ │ ├── SessionStore.java │ │ │ ├── PreKeyBundle.java │ │ │ └── SessionRecord.java │ │ │ ├── DuplicateMessageException.java │ │ │ ├── fingerprint │ │ │ ├── FingerprintParsingException.java │ │ │ ├── FingerprintVersionMismatchException.java │ │ │ ├── FingerprintGenerator.java │ │ │ ├── DisplayableFingerprint.java │ │ │ ├── Fingerprint.java │ │ │ ├── FingerprintIdentifierMismatchException.java │ │ │ ├── NumericFingerprintGenerator.java │ │ │ └── ScannableFingerprint.java │ │ │ ├── NoSessionException.java │ │ │ ├── logging │ │ │ ├── SignalProtocolLoggerProvider.java │ │ │ ├── SignalProtocolLogger.java │ │ │ └── Log.java │ │ │ ├── UntrustedIdentityException.java │ │ │ ├── ecc │ │ │ ├── ECPrivateKey.java │ │ │ ├── ECPublicKey.java │ │ │ ├── ECKeyPair.java │ │ │ ├── DjbECPrivateKey.java │ │ │ ├── DjbECPublicKey.java │ │ │ └── Curve.java │ │ │ ├── InvalidVersionException.java │ │ │ ├── SignalProtocolAddress.java │ │ │ ├── InvalidMacException.java │ │ │ ├── InvalidKeyIdException.java │ │ │ ├── InvalidKeyException.java │ │ │ ├── InvalidMessageException.java │ │ │ ├── protocol │ │ │ ├── CiphertextMessage.java │ │ │ ├── SenderKeyDistributionMessage.java │ │ │ ├── SenderKeyMessage.java │ │ │ ├── KeyExchangeMessage.java │ │ │ ├── PreKeySignalMessage.java │ │ │ └── SignalMessage.java │ │ │ ├── ratchet │ │ │ ├── MessageKeys.java │ │ │ ├── RootKey.java │ │ │ ├── ChainKey.java │ │ │ ├── SymmetricSignalProtocolParameters.java │ │ │ ├── BobSignalProtocolParameters.java │ │ │ └── AliceSignalProtocolParameters.java │ │ │ ├── groups │ │ │ ├── ratchet │ │ │ │ ├── SenderMessageKey.java │ │ │ │ └── SenderChainKey.java │ │ │ ├── SenderKeyName.java │ │ │ ├── state │ │ │ │ ├── SenderKeyStore.java │ │ │ │ ├── SenderKeyRecord.java │ │ │ │ └── SenderKeyState.java │ │ │ └── GroupSessionBuilder.java │ │ │ ├── IdentityKey.java │ │ │ └── IdentityKeyPair.java │ └── test │ │ └── java │ │ └── org │ │ └── whispersystems │ │ └── libsignal │ │ └── CurveTest.java └── build.gradle ├── tests ├── build.gradle └── src │ └── test │ └── java │ └── org │ └── whispersystems │ └── libsignal │ ├── TestInMemoryIdentityKeyStore.java │ ├── TestInMemorySignalProtocolStore.java │ ├── groups │ └── InMemorySenderKeyStore.java │ ├── fingerprint │ └── NumericFingerprintGeneratorTest.java │ └── ratchet │ ├── RootKeyTest.java │ └── ChainKeyTest.java ├── gradlew.bat ├── README.md └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':java', ':android', ':tests' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | /obj 3 | *.iml 4 | .gradle 5 | .idea 6 | gradle.properties 7 | local.properties 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/libsignal-protocol-java/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /protobuf/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | protoc --java_out=../java/src/main/java/ WhisperTextProtocol.proto LocalStorageProtocol.proto FingerprintProtocol.proto 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/StaleKeyExchangeException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public class StaleKeyExchangeException extends Throwable { 4 | } 5 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/Medium.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.util; 2 | 3 | public class Medium { 4 | public static int MAX_VALUE = 0xFFFFFF; 5 | } 6 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/DecryptionCallback.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public interface DecryptionCallback { 4 | public void handlePlaintext(byte[] plaintext); 5 | } -------------------------------------------------------------------------------- /tests/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | repositories { 4 | mavenCentral() 5 | mavenLocal() 6 | } 7 | 8 | dependencies { 9 | testCompile 'junit:junit:3.8.2' 10 | 11 | compile project(':java') 12 | } -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/HKDFv2.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.kdf; 2 | 3 | public class HKDFv2 extends HKDF { 4 | @Override 5 | protected int getIterationStartOffset() { 6 | return 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/HKDFv3.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.kdf; 2 | 3 | public class HKDFv3 extends HKDF { 4 | @Override 5 | protected int getIterationStartOffset() { 6 | return 1; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/LegacyMessageException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public class LegacyMessageException extends Exception { 4 | public LegacyMessageException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SignalProtocolStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | public interface SignalProtocolStore 4 | extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/DuplicateMessageException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public class DuplicateMessageException extends Exception { 4 | public DuplicateMessageException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 09 10:15:39 PST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintParsingException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | public class FingerprintParsingException extends Exception { 4 | 5 | public FingerprintParsingException(Exception nested) { 6 | super(nested); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/NoSessionException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public class NoSessionException extends Exception { 4 | public NoSessionException(String s) { 5 | super(s); 6 | } 7 | 8 | public NoSessionException(Exception nested) { 9 | super(nested); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintVersionMismatchException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | public class FingerprintVersionMismatchException extends Exception { 4 | 5 | public FingerprintVersionMismatchException() { 6 | super(); 7 | } 8 | 9 | public FingerprintVersionMismatchException(Exception e) { 10 | super(e); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintGenerator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | 5 | public interface FingerprintGenerator { 6 | public Fingerprint createFor(String localStableIdentifier, IdentityKey localIdentityKey, 7 | String remoteStableIdentifier, IdentityKey remoteIdentityKey); 8 | } 9 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/logging/SignalProtocolLoggerProvider.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.logging; 2 | 3 | public class SignalProtocolLoggerProvider { 4 | 5 | private static SignalProtocolLogger provider; 6 | 7 | public static SignalProtocolLogger getProvider() { 8 | return provider; 9 | } 10 | 11 | public static void setProvider(SignalProtocolLogger provider) { 12 | SignalProtocolLoggerProvider.provider = provider; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /protobuf/FingerprintProtocol.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.libsignal.fingerprint"; 4 | option java_outer_classname = "FingerprintProtos"; 5 | 6 | message FingerprintData { 7 | optional bytes publicKey = 1; 8 | optional bytes identifier = 2; 9 | } 10 | 11 | message CombinedFingerprint { 12 | optional uint32 version = 1; 13 | optional FingerprintData localFingerprint = 2; 14 | optional FingerprintData remoteFingerprint = 3; 15 | } -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/logging/SignalProtocolLogger.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.logging; 2 | 3 | public interface SignalProtocolLogger { 4 | 5 | public static final int VERBOSE = 2; 6 | public static final int DEBUG = 3; 7 | public static final int INFO = 4; 8 | public static final int WARN = 5; 9 | public static final int ERROR = 6; 10 | public static final int ASSERT = 7; 11 | 12 | public void log(int priority, String tag, String message); 13 | } 14 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/UntrustedIdentityException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public class UntrustedIdentityException extends Exception { 4 | 5 | private final String name; 6 | private final IdentityKey key; 7 | 8 | public UntrustedIdentityException(String name, IdentityKey key) { 9 | this.name = name; 10 | this.key = key; 11 | } 12 | 13 | public IdentityKey getUntrustedIdentity() { 14 | return key; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/DerivedRootSecrets.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.kdf; 2 | 3 | import org.whispersystems.libsignal.util.ByteUtil; 4 | 5 | public class DerivedRootSecrets { 6 | 7 | public static final int SIZE = 64; 8 | 9 | private final byte[] rootKey; 10 | private final byte[] chainKey; 11 | 12 | public DerivedRootSecrets(byte[] okm) { 13 | byte[][] keys = ByteUtil.split(okm, 32, 32); 14 | this.rootKey = keys[0]; 15 | this.chainKey = keys[1]; 16 | } 17 | 18 | public byte[] getRootKey() { 19 | return rootKey; 20 | } 21 | 22 | public byte[] getChainKey() { 23 | return chainKey; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/DisplayableFingerprint.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | public class DisplayableFingerprint { 4 | 5 | private final String localFingerprint; 6 | private final String remoteFingerprint; 7 | 8 | public DisplayableFingerprint(String localFingerprint, String remoteFingerprint) { 9 | this.localFingerprint = localFingerprint; 10 | this.remoteFingerprint = remoteFingerprint; 11 | } 12 | 13 | public String getDisplayText() { 14 | if (localFingerprint.compareTo(remoteFingerprint) <= 0) { 15 | return localFingerprint + remoteFingerprint; 16 | } else { 17 | return remoteFingerprint + localFingerprint; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/src/test/java/org/whispersystems/libsignal/CurveTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import org.whispersystems.libsignal.ecc.Curve; 6 | import org.whispersystems.libsignal.ecc.ECKeyPair; 7 | 8 | public class CurveTest extends TestCase { 9 | 10 | public void testPureJava() { 11 | assertFalse(Curve.isNative()); 12 | } 13 | 14 | public void testLargeSignatures() throws InvalidKeyException { 15 | ECKeyPair keys = Curve.generateKeyPair(); 16 | byte[] message = new byte[1024 * 1024]; 17 | byte[] signature = Curve.calculateSignature(keys.getPrivateKey(), message); 18 | 19 | assertTrue(Curve.verifySignature(keys.getPublicKey(), message, signature)); 20 | 21 | message[0] ^= 0x01; 22 | 23 | assertFalse(Curve.verifySignature(keys.getPublicKey(), message, signature)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /android/src/androidTest/java/org/whispersystems/libsignal/CurveTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import org.whispersystems.libsignal.ecc.Curve; 6 | import org.whispersystems.libsignal.ecc.ECKeyPair; 7 | 8 | public class CurveTest extends TestCase { 9 | 10 | public void testPureJava() { 11 | assertTrue(Curve.isNative()); 12 | } 13 | 14 | public void testLargeSignatures() throws InvalidKeyException { 15 | ECKeyPair keys = Curve.generateKeyPair(); 16 | byte[] message = new byte[1024 * 1024]; 17 | byte[] signature = Curve.calculateSignature(keys.getPrivateKey(), message); 18 | 19 | assertTrue(Curve.verifySignature(keys.getPublicKey(), message, signature)); 20 | 21 | message[0] ^= 0x01; 22 | 23 | assertFalse(Curve.verifySignature(keys.getPublicKey(), message, signature)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/java/org/whispersystems/libsignal/util/AndroidSignalProtocolLogger.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.util; 2 | 3 | import android.util.Log; 4 | import android.util.SparseIntArray; 5 | 6 | import org.whispersystems.libsignal.logging.SignalProtocolLogger; 7 | 8 | public class AndroidSignalProtocolLogger implements SignalProtocolLogger { 9 | 10 | private static final SparseIntArray PRIORITY_MAP = new SparseIntArray(5) {{ 11 | put(SignalProtocolLogger.INFO, Log.INFO); 12 | put(SignalProtocolLogger.ASSERT, Log.ASSERT); 13 | put(SignalProtocolLogger.DEBUG, Log.DEBUG); 14 | put(SignalProtocolLogger.VERBOSE, Log.VERBOSE); 15 | put(SignalProtocolLogger.WARN, Log.WARN); 16 | 17 | }}; 18 | 19 | @Override 20 | public void log(int priority, String tag, String message) { 21 | int androidPriority = PRIORITY_MAP.get(priority, Log.WARN); 22 | Log.println(androidPriority, tag, message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/TestInMemoryIdentityKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | import org.whispersystems.libsignal.ecc.Curve; 4 | import org.whispersystems.libsignal.ecc.ECKeyPair; 5 | import org.whispersystems.libsignal.util.KeyHelper; 6 | 7 | public class TestInMemoryIdentityKeyStore extends org.whispersystems.libsignal.state.impl.InMemoryIdentityKeyStore { 8 | public TestInMemoryIdentityKeyStore() { 9 | super(generateIdentityKeyPair(), generateRegistrationId()); 10 | } 11 | 12 | private static IdentityKeyPair generateIdentityKeyPair() { 13 | ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); 14 | 15 | return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), 16 | identityKeyPairKeys.getPrivateKey()); 17 | } 18 | 19 | private static int generateRegistrationId() { 20 | return KeyHelper.generateRegistrationId(false); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/ECPrivateKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.ecc; 19 | 20 | public interface ECPrivateKey { 21 | public byte[] serialize(); 22 | public int getType(); 23 | } 24 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/Fingerprint.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | public class Fingerprint { 4 | 5 | private final DisplayableFingerprint displayableFingerprint; 6 | private final ScannableFingerprint scannableFingerprint; 7 | 8 | public Fingerprint(DisplayableFingerprint displayableFingerprint, 9 | ScannableFingerprint scannableFingerprint) 10 | { 11 | this.displayableFingerprint = displayableFingerprint; 12 | this.scannableFingerprint = scannableFingerprint; 13 | } 14 | 15 | /** 16 | * @return A text fingerprint that can be displayed and compared remotely. 17 | */ 18 | public DisplayableFingerprint getDisplayableFingerprint() { 19 | return displayableFingerprint; 20 | } 21 | 22 | /** 23 | * @return A scannable fingerprint that can be scanned anc compared locally. 24 | */ 25 | public ScannableFingerprint getScannableFingerprint() { 26 | return scannableFingerprint; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/TestInMemorySignalProtocolStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | import org.whispersystems.libsignal.ecc.Curve; 4 | import org.whispersystems.libsignal.ecc.ECKeyPair; 5 | import org.whispersystems.libsignal.state.impl.InMemorySignalProtocolStore; 6 | import org.whispersystems.libsignal.util.KeyHelper; 7 | 8 | public class TestInMemorySignalProtocolStore extends InMemorySignalProtocolStore { 9 | public TestInMemorySignalProtocolStore() { 10 | super(generateIdentityKeyPair(), generateRegistrationId()); 11 | } 12 | 13 | private static IdentityKeyPair generateIdentityKeyPair() { 14 | ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); 15 | 16 | return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), 17 | identityKeyPairKeys.getPrivateKey()); 18 | } 19 | 20 | private static int generateRegistrationId() { 21 | return KeyHelper.generateRegistrationId(false); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidVersionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | public class InvalidVersionException extends Exception { 20 | public InvalidVersionException(String detailMessage) { 21 | super(detailMessage); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/ECPublicKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.ecc; 19 | 20 | public interface ECPublicKey extends Comparable { 21 | 22 | public static final int KEY_SIZE = 33; 23 | 24 | public byte[] serialize(); 25 | 26 | public int getType(); 27 | } 28 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/SignalProtocolAddress.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal; 2 | 3 | public class SignalProtocolAddress { 4 | 5 | private final String name; 6 | private final int deviceId; 7 | 8 | public SignalProtocolAddress(String name, int deviceId) { 9 | this.name = name; 10 | this.deviceId = deviceId; 11 | } 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public int getDeviceId() { 18 | return deviceId; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return name + ":" + deviceId; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object other) { 28 | if (other == null) return false; 29 | if (!(other instanceof SignalProtocolAddress)) return false; 30 | 31 | SignalProtocolAddress that = (SignalProtocolAddress)other; 32 | return this.name.equals(that.name) && this.deviceId == that.deviceId; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return this.name.hashCode() ^ this.deviceId; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/groups/InMemorySenderKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.groups; 2 | 3 | import org.whispersystems.libsignal.groups.state.SenderKeyRecord; 4 | import org.whispersystems.libsignal.groups.state.SenderKeyStore; 5 | 6 | import java.io.IOException; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class InMemorySenderKeyStore implements SenderKeyStore { 11 | 12 | private final Map store = new HashMap<>(); 13 | 14 | @Override 15 | public void storeSenderKey(SenderKeyName senderKeyName, SenderKeyRecord record) { 16 | store.put(senderKeyName, record); 17 | } 18 | 19 | @Override 20 | public SenderKeyRecord loadSenderKey(SenderKeyName senderKeyName) { 21 | try { 22 | SenderKeyRecord record = store.get(senderKeyName); 23 | 24 | if (record == null) { 25 | return new SenderKeyRecord(); 26 | } else { 27 | return new SenderKeyRecord(record.serialize()); 28 | } 29 | } catch (IOException e) { 30 | throw new AssertionError(e); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidMacException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | public class InvalidMacException extends Exception { 20 | 21 | public InvalidMacException(String detailMessage) { 22 | super(detailMessage); 23 | } 24 | 25 | public InvalidMacException(Throwable throwable) { 26 | super(throwable); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidKeyIdException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | public class InvalidKeyIdException extends Exception { 20 | public InvalidKeyIdException(String detailMessage) { 21 | super(detailMessage); 22 | } 23 | 24 | public InvalidKeyIdException(Throwable throwable) { 25 | super(throwable); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintIdentifierMismatchException.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | public class FingerprintIdentifierMismatchException extends Exception { 4 | 5 | private final String localIdentifier; 6 | private final String remoteIdentifier; 7 | private final String scannedLocalIdentifier; 8 | private final String scannedRemoteIdentifier; 9 | 10 | public FingerprintIdentifierMismatchException(String localIdentifier, String remoteIdentifier, 11 | String scannedLocalIdentifier, String scannedRemoteIdentifier) 12 | { 13 | this.localIdentifier = localIdentifier; 14 | this.remoteIdentifier = remoteIdentifier; 15 | this.scannedLocalIdentifier = scannedLocalIdentifier; 16 | this.scannedRemoteIdentifier = scannedRemoteIdentifier; 17 | } 18 | 19 | public String getScannedRemoteIdentifier() { 20 | return scannedRemoteIdentifier; 21 | } 22 | 23 | public String getScannedLocalIdentifier() { 24 | return scannedLocalIdentifier; 25 | } 26 | 27 | public String getRemoteIdentifier() { 28 | return remoteIdentifier; 29 | } 30 | 31 | public String getLocalIdentifier() { 32 | return localIdentifier; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidKeyException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | public class InvalidKeyException extends Exception { 20 | 21 | public InvalidKeyException() {} 22 | 23 | public InvalidKeyException(String detailMessage) { 24 | super(detailMessage); 25 | } 26 | 27 | public InvalidKeyException(Throwable throwable) { 28 | super(throwable); 29 | } 30 | 31 | public InvalidKeyException(String detailMessage, Throwable throwable) { 32 | super(detailMessage, throwable); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /protobuf/WhisperTextProtocol.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.libsignal.protocol"; 4 | option java_outer_classname = "SignalProtos"; 5 | 6 | message SignalMessage { 7 | optional bytes ratchetKey = 1; 8 | optional uint32 counter = 2; 9 | optional uint32 previousCounter = 3; 10 | optional bytes ciphertext = 4; 11 | } 12 | 13 | message PreKeySignalMessage { 14 | optional uint32 registrationId = 5; 15 | optional uint32 preKeyId = 1; 16 | optional uint32 signedPreKeyId = 6; 17 | optional bytes baseKey = 2; 18 | optional bytes identityKey = 3; 19 | optional bytes message = 4; // WhisperMessage 20 | } 21 | 22 | message KeyExchangeMessage { 23 | optional uint32 id = 1; 24 | optional bytes baseKey = 2; 25 | optional bytes ratchetKey = 3; 26 | optional bytes identityKey = 4; 27 | optional bytes baseKeySignature = 5; 28 | } 29 | 30 | message SenderKeyMessage { 31 | optional uint32 id = 1; 32 | optional uint32 iteration = 2; 33 | optional bytes ciphertext = 3; 34 | } 35 | 36 | message SenderKeyDistributionMessage { 37 | optional uint32 id = 1; 38 | optional uint32 iteration = 2; 39 | optional bytes chainKey = 3; 40 | optional bytes signingKey = 4; 41 | } -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/ECKeyPair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.ecc; 19 | 20 | public class ECKeyPair { 21 | 22 | private final ECPublicKey publicKey; 23 | private final ECPrivateKey privateKey; 24 | 25 | public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { 26 | this.publicKey = publicKey; 27 | this.privateKey = privateKey; 28 | } 29 | 30 | public ECPublicKey getPublicKey() { 31 | return publicKey; 32 | } 33 | 34 | public ECPrivateKey getPrivateKey() { 35 | return privateKey; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemoryPreKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state.impl; 2 | 3 | import org.whispersystems.libsignal.InvalidKeyIdException; 4 | import org.whispersystems.libsignal.state.PreKeyRecord; 5 | import org.whispersystems.libsignal.state.PreKeyStore; 6 | 7 | import java.io.IOException; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class InMemoryPreKeyStore implements PreKeyStore { 12 | 13 | private final Map store = new HashMap<>(); 14 | 15 | @Override 16 | public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { 17 | try { 18 | if (!store.containsKey(preKeyId)) { 19 | throw new InvalidKeyIdException("No such prekeyrecord!"); 20 | } 21 | 22 | return new PreKeyRecord(store.get(preKeyId)); 23 | } catch (IOException e) { 24 | throw new AssertionError(e); 25 | } 26 | } 27 | 28 | @Override 29 | public void storePreKey(int preKeyId, PreKeyRecord record) { 30 | store.put(preKeyId, record.serialize()); 31 | } 32 | 33 | @Override 34 | public boolean containsPreKey(int preKeyId) { 35 | return store.containsKey(preKeyId); 36 | } 37 | 38 | @Override 39 | public void removePreKey(int preKeyId) { 40 | store.remove(preKeyId); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/DjbECPrivateKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.ecc; 19 | 20 | public class DjbECPrivateKey implements ECPrivateKey { 21 | 22 | private final byte[] privateKey; 23 | 24 | DjbECPrivateKey(byte[] privateKey) { 25 | this.privateKey = privateKey; 26 | } 27 | 28 | @Override 29 | public byte[] serialize() { 30 | return privateKey; 31 | } 32 | 33 | @Override 34 | public int getType() { 35 | return Curve.DJB_TYPE; 36 | } 37 | 38 | public byte[] getPrivateKey() { 39 | return privateKey; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/PreKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import org.whispersystems.libsignal.InvalidKeyIdException; 4 | 5 | /** 6 | * An interface describing the local storage of {@link PreKeyRecord}s. 7 | * 8 | * @author Moxie Marlinspike 9 | */ 10 | public interface PreKeyStore { 11 | 12 | /** 13 | * Load a local PreKeyRecord. 14 | * 15 | * @param preKeyId the ID of the local PreKeyRecord. 16 | * @return the corresponding PreKeyRecord. 17 | * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. 18 | */ 19 | public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException; 20 | 21 | /** 22 | * Store a local PreKeyRecord. 23 | * 24 | * @param preKeyId the ID of the PreKeyRecord to store. 25 | * @param record the PreKeyRecord. 26 | */ 27 | public void storePreKey(int preKeyId, PreKeyRecord record); 28 | 29 | /** 30 | * @param preKeyId A PreKeyRecord ID. 31 | * @return true if the store has a record for the preKeyId, otherwise false. 32 | */ 33 | public boolean containsPreKey(int preKeyId); 34 | 35 | /** 36 | * Delete a PreKeyRecord from local storage. 37 | * 38 | * @param preKeyId The ID of the PreKeyRecord to remove. 39 | */ 40 | public void removePreKey(int preKeyId); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/guava/Supplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.whispersystems.libsignal.util.guava; 18 | 19 | 20 | /** 21 | * A class that can supply objects of a single type. Semantically, this could 22 | * be a factory, generator, builder, closure, or something else entirely. No 23 | * guarantees are implied by this interface. 24 | * 25 | * @author Harry Heymann 26 | * @since 2.0 (imported from Google Collections Library) 27 | */ 28 | public interface Supplier { 29 | /** 30 | * Retrieves an instance of the appropriate type. The returned object may or 31 | * may not be a new instance, depending on the implementation. 32 | * 33 | * @return an instance of the appropriate type 34 | */ 35 | T get(); 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidMessageException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | import java.util.List; 20 | 21 | public class InvalidMessageException extends Exception { 22 | 23 | public InvalidMessageException() {} 24 | 25 | public InvalidMessageException(String detailMessage) { 26 | super(detailMessage); 27 | } 28 | 29 | public InvalidMessageException(Throwable throwable) { 30 | super(throwable); 31 | } 32 | 33 | public InvalidMessageException(String detailMessage, Throwable throwable) { 34 | super(detailMessage, throwable); 35 | } 36 | 37 | public InvalidMessageException(String detailMessage, List exceptions) { 38 | super(detailMessage, exceptions.get(0)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/CiphertextMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.protocol; 18 | 19 | public interface CiphertextMessage { 20 | 21 | public static final int UNSUPPORTED_VERSION = 1; 22 | public static final int CURRENT_VERSION = 3; 23 | 24 | public static final int WHISPER_TYPE = 2; 25 | public static final int PREKEY_TYPE = 3; 26 | public static final int SENDERKEY_TYPE = 4; 27 | public static final int SENDERKEY_DISTRIBUTION_TYPE = 5; 28 | 29 | // This should be the worst case (worse than V2). So not always accurate, but good enough for padding. 30 | public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53; 31 | 32 | public byte[] serialize(); 33 | public int getType(); 34 | 35 | } -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SignedPreKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import org.whispersystems.libsignal.InvalidKeyIdException; 4 | 5 | import java.util.List; 6 | 7 | public interface SignedPreKeyStore { 8 | 9 | 10 | /** 11 | * Load a local SignedPreKeyRecord. 12 | * 13 | * @param signedPreKeyId the ID of the local SignedPreKeyRecord. 14 | * @return the corresponding SignedPreKeyRecord. 15 | * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. 16 | */ 17 | public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException; 18 | 19 | /** 20 | * Load all local SignedPreKeyRecords. 21 | * 22 | * @return All stored SignedPreKeyRecords. 23 | */ 24 | public List loadSignedPreKeys(); 25 | 26 | /** 27 | * Store a local SignedPreKeyRecord. 28 | * 29 | * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. 30 | * @param record the SignedPreKeyRecord. 31 | */ 32 | public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record); 33 | 34 | /** 35 | * @param signedPreKeyId A SignedPreKeyRecord ID. 36 | * @return true if the store has a record for the signedPreKeyId, otherwise false. 37 | */ 38 | public boolean containsSignedPreKey(int signedPreKeyId); 39 | 40 | /** 41 | * Delete a SignedPreKeyRecord from local storage. 42 | * 43 | * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. 44 | */ 45 | public void removeSignedPreKey(int signedPreKeyId); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemoryIdentityKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state.impl; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.IdentityKeyPair; 5 | import org.whispersystems.libsignal.ecc.Curve; 6 | import org.whispersystems.libsignal.ecc.ECKeyPair; 7 | import org.whispersystems.libsignal.state.IdentityKeyStore; 8 | 9 | import java.security.NoSuchAlgorithmException; 10 | import java.security.SecureRandom; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class InMemoryIdentityKeyStore implements IdentityKeyStore { 15 | 16 | private final Map trustedKeys = new HashMap<>(); 17 | 18 | private final IdentityKeyPair identityKeyPair; 19 | private final int localRegistrationId; 20 | 21 | public InMemoryIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) { 22 | this.identityKeyPair = identityKeyPair; 23 | this.localRegistrationId = localRegistrationId; 24 | } 25 | 26 | @Override 27 | public IdentityKeyPair getIdentityKeyPair() { 28 | return identityKeyPair; 29 | } 30 | 31 | @Override 32 | public int getLocalRegistrationId() { 33 | return localRegistrationId; 34 | } 35 | 36 | @Override 37 | public void saveIdentity(String name, IdentityKey identityKey) { 38 | trustedKeys.put(name, identityKey); 39 | } 40 | 41 | @Override 42 | public boolean isTrustedIdentity(String name, IdentityKey identityKey) { 43 | IdentityKey trusted = trustedKeys.get(name); 44 | return (trusted == null || trusted.equals(identityKey)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/Pair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.util; 18 | 19 | public class Pair { 20 | private final T1 v1; 21 | private final T2 v2; 22 | 23 | public Pair(T1 v1, T2 v2) { 24 | this.v1 = v1; 25 | this.v2 = v2; 26 | } 27 | 28 | public T1 first(){ 29 | return v1; 30 | } 31 | 32 | public T2 second(){ 33 | return v2; 34 | } 35 | 36 | public boolean equals(Object o) { 37 | return o instanceof Pair && 38 | equal(((Pair) o).first(), first()) && 39 | equal(((Pair) o).second(), second()); 40 | } 41 | 42 | public int hashCode() { 43 | return first().hashCode() ^ second().hashCode(); 44 | } 45 | 46 | private boolean equal(Object first, Object second) { 47 | if (first == null && second == null) return true; 48 | if (first == null || second == null) return false; 49 | return first.equals(second); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/MessageKeys.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.ratchet; 18 | 19 | import javax.crypto.spec.IvParameterSpec; 20 | import javax.crypto.spec.SecretKeySpec; 21 | 22 | public class MessageKeys { 23 | 24 | private final SecretKeySpec cipherKey; 25 | private final SecretKeySpec macKey; 26 | private final IvParameterSpec iv; 27 | private final int counter; 28 | 29 | public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, IvParameterSpec iv, int counter) { 30 | this.cipherKey = cipherKey; 31 | this.macKey = macKey; 32 | this.iv = iv; 33 | this.counter = counter; 34 | } 35 | 36 | public SecretKeySpec getCipherKey() { 37 | return cipherKey; 38 | } 39 | 40 | public SecretKeySpec getMacKey() { 41 | return macKey; 42 | } 43 | 44 | public IvParameterSpec getIv() { 45 | return iv; 46 | } 47 | 48 | public int getCounter() { 49 | return counter; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemorySignedPreKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state.impl; 2 | 3 | import org.whispersystems.libsignal.InvalidKeyIdException; 4 | import org.whispersystems.libsignal.state.SignedPreKeyRecord; 5 | import org.whispersystems.libsignal.state.SignedPreKeyStore; 6 | 7 | import java.io.IOException; 8 | import java.util.HashMap; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class InMemorySignedPreKeyStore implements SignedPreKeyStore { 14 | 15 | private final Map store = new HashMap<>(); 16 | 17 | @Override 18 | public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { 19 | try { 20 | if (!store.containsKey(signedPreKeyId)) { 21 | throw new InvalidKeyIdException("No such signedprekeyrecord! " + signedPreKeyId); 22 | } 23 | 24 | return new SignedPreKeyRecord(store.get(signedPreKeyId)); 25 | } catch (IOException e) { 26 | throw new AssertionError(e); 27 | } 28 | } 29 | 30 | @Override 31 | public List loadSignedPreKeys() { 32 | try { 33 | List results = new LinkedList<>(); 34 | 35 | for (byte[] serialized : store.values()) { 36 | results.add(new SignedPreKeyRecord(serialized)); 37 | } 38 | 39 | return results; 40 | } catch (IOException e) { 41 | throw new AssertionError(e); 42 | } 43 | } 44 | 45 | @Override 46 | public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { 47 | store.put(signedPreKeyId, record.serialize()); 48 | } 49 | 50 | @Override 51 | public boolean containsSignedPreKey(int signedPreKeyId) { 52 | return store.containsKey(signedPreKeyId); 53 | } 54 | 55 | @Override 56 | public void removeSignedPreKey(int signedPreKeyId) { 57 | store.remove(signedPreKeyId); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/ratchet/SenderMessageKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.groups.ratchet; 19 | 20 | import org.whispersystems.libsignal.kdf.HKDFv3; 21 | import org.whispersystems.libsignal.util.ByteUtil; 22 | 23 | /** 24 | * The final symmetric material (IV and Cipher Key) used for encrypting 25 | * individual SenderKey messages. 26 | * 27 | * @author Moxie Marlinspike 28 | */ 29 | public class SenderMessageKey { 30 | 31 | private final int iteration; 32 | private final byte[] iv; 33 | private final byte[] cipherKey; 34 | private final byte[] seed; 35 | 36 | public SenderMessageKey(int iteration, byte[] seed) { 37 | byte[] derivative = new HKDFv3().deriveSecrets(seed, "WhisperGroup".getBytes(), 48); 38 | byte[][] parts = ByteUtil.split(derivative, 16, 32); 39 | 40 | this.iteration = iteration; 41 | this.seed = seed; 42 | this.iv = parts[0]; 43 | this.cipherKey = parts[1]; 44 | } 45 | 46 | public int getIteration() { 47 | return iteration; 48 | } 49 | 50 | public byte[] getIv() { 51 | return iv; 52 | } 53 | 54 | public byte[] getCipherKey() { 55 | return cipherKey; 56 | } 57 | 58 | public byte[] getSeed() { 59 | return seed; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/PreKeyRecord.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import com.google.protobuf.ByteString; 4 | 5 | import org.whispersystems.libsignal.InvalidKeyException; 6 | import org.whispersystems.libsignal.ecc.Curve; 7 | import org.whispersystems.libsignal.ecc.ECKeyPair; 8 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 9 | import org.whispersystems.libsignal.ecc.ECPublicKey; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.whispersystems.libsignal.state.StorageProtos.PreKeyRecordStructure; 14 | 15 | public class PreKeyRecord { 16 | 17 | private PreKeyRecordStructure structure; 18 | 19 | public PreKeyRecord(int id, ECKeyPair keyPair) { 20 | this.structure = PreKeyRecordStructure.newBuilder() 21 | .setId(id) 22 | .setPublicKey(ByteString.copyFrom(keyPair.getPublicKey() 23 | .serialize())) 24 | .setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey() 25 | .serialize())) 26 | .build(); 27 | } 28 | 29 | public PreKeyRecord(byte[] serialized) throws IOException { 30 | this.structure = PreKeyRecordStructure.parseFrom(serialized); 31 | } 32 | 33 | public int getId() { 34 | return this.structure.getId(); 35 | } 36 | 37 | public ECKeyPair getKeyPair() { 38 | try { 39 | ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0); 40 | ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray()); 41 | 42 | return new ECKeyPair(publicKey, privateKey); 43 | } catch (InvalidKeyException e) { 44 | throw new AssertionError(e); 45 | } 46 | } 47 | 48 | public byte[] serialize() { 49 | return this.structure.toByteArray(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/IdentityKeyStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.IdentityKeyPair; 5 | 6 | /** 7 | * Provides an interface to identity information. 8 | * 9 | * @author Moxie Marlinspike 10 | */ 11 | public interface IdentityKeyStore { 12 | 13 | /** 14 | * Get the local client's identity key pair. 15 | * 16 | * @return The local client's persistent identity key pair. 17 | */ 18 | public IdentityKeyPair getIdentityKeyPair(); 19 | 20 | /** 21 | * Return the local client's registration ID. 22 | *

23 | * Clients should maintain a registration ID, a random number 24 | * between 1 and 16380 that's generated once at install time. 25 | * 26 | * @return the local client's registration ID. 27 | */ 28 | public int getLocalRegistrationId(); 29 | 30 | /** 31 | * Save a remote client's identity key 32 | *

33 | * Store a remote client's identity key as trusted. 34 | * 35 | * @param name The name of the remote client. 36 | * @param identityKey The remote client's identity key. 37 | */ 38 | public void saveIdentity(String name, IdentityKey identityKey); 39 | 40 | 41 | /** 42 | * Verify a remote client's identity key. 43 | *

44 | * Determine whether a remote client's identity is trusted. Convention is 45 | * that the TextSecure protocol is 'trust on first use.' This means that 46 | * an identity key is considered 'trusted' if there is no entry for the recipient 47 | * in the local store, or if it matches the saved key for a recipient in the local 48 | * store. Only if it mismatches an entry in the local store is it considered 49 | * 'untrusted.' 50 | * 51 | * @param name The name of the remote client. 52 | * @param identityKey The identity key to verify. 53 | * @return true if trusted, false if untrusted. 54 | */ 55 | public boolean isTrustedIdentity(String name, IdentityKey identityKey); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/IdentityKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | 20 | import org.whispersystems.libsignal.ecc.Curve; 21 | import org.whispersystems.libsignal.ecc.ECPublicKey; 22 | import org.whispersystems.libsignal.util.Hex; 23 | 24 | /** 25 | * A class for representing an identity key. 26 | * 27 | * @author Moxie Marlinspike 28 | */ 29 | 30 | public class IdentityKey { 31 | 32 | private final ECPublicKey publicKey; 33 | 34 | public IdentityKey(ECPublicKey publicKey) { 35 | this.publicKey = publicKey; 36 | } 37 | 38 | public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException { 39 | this.publicKey = Curve.decodePoint(bytes, offset); 40 | } 41 | 42 | public ECPublicKey getPublicKey() { 43 | return publicKey; 44 | } 45 | 46 | public byte[] serialize() { 47 | return publicKey.serialize(); 48 | } 49 | 50 | public String getFingerprint() { 51 | return Hex.toString(publicKey.serialize()); 52 | } 53 | 54 | @Override 55 | public boolean equals(Object other) { 56 | if (other == null) return false; 57 | if (!(other instanceof IdentityKey)) return false; 58 | 59 | return publicKey.equals(((IdentityKey) other).getPublicKey()); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return publicKey.hashCode(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/DjbECPublicKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.ecc; 19 | 20 | import org.whispersystems.libsignal.util.ByteUtil; 21 | 22 | import java.math.BigInteger; 23 | import java.util.Arrays; 24 | 25 | public class DjbECPublicKey implements ECPublicKey { 26 | 27 | private final byte[] publicKey; 28 | 29 | DjbECPublicKey(byte[] publicKey) { 30 | this.publicKey = publicKey; 31 | } 32 | 33 | @Override 34 | public byte[] serialize() { 35 | byte[] type = {Curve.DJB_TYPE}; 36 | return ByteUtil.combine(type, publicKey); 37 | } 38 | 39 | @Override 40 | public int getType() { 41 | return Curve.DJB_TYPE; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object other) { 46 | if (other == null) return false; 47 | if (!(other instanceof DjbECPublicKey)) return false; 48 | 49 | DjbECPublicKey that = (DjbECPublicKey)other; 50 | return Arrays.equals(this.publicKey, that.publicKey); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Arrays.hashCode(publicKey); 56 | } 57 | 58 | @Override 59 | public int compareTo(ECPublicKey another) { 60 | return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey)); 61 | } 62 | 63 | public byte[] getPublicKey() { 64 | return publicKey; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/SenderKeyName.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.groups; 18 | 19 | import org.whispersystems.libsignal.SignalProtocolAddress; 20 | 21 | /** 22 | * A representation of a (groupId + senderId + deviceId) tuple. 23 | */ 24 | public class SenderKeyName { 25 | 26 | private final String groupId; 27 | private final SignalProtocolAddress sender; 28 | 29 | public SenderKeyName(String groupId, SignalProtocolAddress sender) { 30 | this.groupId = groupId; 31 | this.sender = sender; 32 | } 33 | 34 | public String getGroupId() { 35 | return groupId; 36 | } 37 | 38 | public SignalProtocolAddress getSender() { 39 | return sender; 40 | } 41 | 42 | public String serialize() { 43 | return groupId + "::" + sender.getName() + "::" + String.valueOf(sender.getDeviceId()); 44 | } 45 | 46 | @Override 47 | public boolean equals(Object other) { 48 | if (other == null) return false; 49 | if (!(other instanceof SenderKeyName)) return false; 50 | 51 | SenderKeyName that = (SenderKeyName)other; 52 | 53 | return 54 | this.groupId.equals(that.groupId) && 55 | this.sender.equals(that.sender); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return this.groupId.hashCode() ^ this.sender.hashCode(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/DerivedMessageSecrets.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.kdf; 19 | 20 | import org.whispersystems.libsignal.util.ByteUtil; 21 | 22 | import java.text.ParseException; 23 | 24 | import javax.crypto.spec.IvParameterSpec; 25 | import javax.crypto.spec.SecretKeySpec; 26 | 27 | public class DerivedMessageSecrets { 28 | 29 | public static final int SIZE = 80; 30 | private static final int CIPHER_KEY_LENGTH = 32; 31 | private static final int MAC_KEY_LENGTH = 32; 32 | private static final int IV_LENGTH = 16; 33 | 34 | private final SecretKeySpec cipherKey; 35 | private final SecretKeySpec macKey; 36 | private final IvParameterSpec iv; 37 | 38 | public DerivedMessageSecrets(byte[] okm) { 39 | try { 40 | byte[][] keys = ByteUtil.split(okm, CIPHER_KEY_LENGTH, MAC_KEY_LENGTH, IV_LENGTH); 41 | 42 | this.cipherKey = new SecretKeySpec(keys[0], "AES"); 43 | this.macKey = new SecretKeySpec(keys[1], "HmacSHA256"); 44 | this.iv = new IvParameterSpec(keys[2]); 45 | } catch (ParseException e) { 46 | throw new AssertionError(e); 47 | } 48 | } 49 | 50 | public SecretKeySpec getCipherKey() { 51 | return cipherKey; 52 | } 53 | 54 | public SecretKeySpec getMacKey() { 55 | return macKey; 56 | } 57 | 58 | public IvParameterSpec getIv() { 59 | return iv; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemorySessionStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state.impl; 2 | 3 | import org.whispersystems.libsignal.SignalProtocolAddress; 4 | import org.whispersystems.libsignal.state.SessionRecord; 5 | import org.whispersystems.libsignal.state.SessionStore; 6 | 7 | import java.io.IOException; 8 | import java.util.HashMap; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class InMemorySessionStore implements SessionStore { 14 | 15 | private Map sessions = new HashMap<>(); 16 | 17 | public InMemorySessionStore() {} 18 | 19 | @Override 20 | public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) { 21 | try { 22 | if (containsSession(remoteAddress)) { 23 | return new SessionRecord(sessions.get(remoteAddress)); 24 | } else { 25 | return new SessionRecord(); 26 | } 27 | } catch (IOException e) { 28 | throw new AssertionError(e); 29 | } 30 | } 31 | 32 | @Override 33 | public synchronized List getSubDeviceSessions(String name) { 34 | List deviceIds = new LinkedList<>(); 35 | 36 | for (SignalProtocolAddress key : sessions.keySet()) { 37 | if (key.getName().equals(name) && 38 | key.getDeviceId() != 1) 39 | { 40 | deviceIds.add(key.getDeviceId()); 41 | } 42 | } 43 | 44 | return deviceIds; 45 | } 46 | 47 | @Override 48 | public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) { 49 | sessions.put(address, record.serialize()); 50 | } 51 | 52 | @Override 53 | public synchronized boolean containsSession(SignalProtocolAddress address) { 54 | return sessions.containsKey(address); 55 | } 56 | 57 | @Override 58 | public synchronized void deleteSession(SignalProtocolAddress address) { 59 | sessions.remove(address); 60 | } 61 | 62 | @Override 63 | public synchronized void deleteAllSessions(String name) { 64 | for (SignalProtocolAddress key : sessions.keySet()) { 65 | if (key.getName().equals(name)) { 66 | sessions.remove(key); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/RootKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.ratchet; 18 | 19 | import org.whispersystems.libsignal.InvalidKeyException; 20 | import org.whispersystems.libsignal.ecc.Curve; 21 | import org.whispersystems.libsignal.ecc.ECKeyPair; 22 | import org.whispersystems.libsignal.ecc.ECPublicKey; 23 | import org.whispersystems.libsignal.kdf.DerivedRootSecrets; 24 | import org.whispersystems.libsignal.kdf.HKDF; 25 | import org.whispersystems.libsignal.util.ByteUtil; 26 | import org.whispersystems.libsignal.util.Pair; 27 | 28 | public class RootKey { 29 | 30 | private final HKDF kdf; 31 | private final byte[] key; 32 | 33 | public RootKey(HKDF kdf, byte[] key) { 34 | this.kdf = kdf; 35 | this.key = key; 36 | } 37 | 38 | public byte[] getKeyBytes() { 39 | return key; 40 | } 41 | 42 | public Pair createChain(ECPublicKey theirRatchetKey, ECKeyPair ourRatchetKey) 43 | throws InvalidKeyException 44 | { 45 | byte[] sharedSecret = Curve.calculateAgreement(theirRatchetKey, ourRatchetKey.getPrivateKey()); 46 | byte[] derivedSecretBytes = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes(), DerivedRootSecrets.SIZE); 47 | DerivedRootSecrets derivedSecrets = new DerivedRootSecrets(derivedSecretBytes); 48 | 49 | RootKey newRootKey = new RootKey(kdf, derivedSecrets.getRootKey()); 50 | ChainKey newChainKey = new ChainKey(kdf, derivedSecrets.getChainKey(), 0); 51 | 52 | return new Pair<>(newRootKey, newChainKey); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.groups.state; 18 | 19 | import org.whispersystems.libsignal.groups.SenderKeyName; 20 | 21 | public interface SenderKeyStore { 22 | 23 | /** 24 | * Commit to storage the {@link org.whispersystems.libsignal.groups.state.SenderKeyRecord} for a 25 | * given (groupId + senderId + deviceId) tuple. 26 | * 27 | * @param senderKeyName the (groupId + senderId + deviceId) tuple. 28 | * @param record the current SenderKeyRecord for the specified senderKeyName. 29 | */ 30 | public void storeSenderKey(SenderKeyName senderKeyName, SenderKeyRecord record); 31 | 32 | /** 33 | * Returns a copy of the {@link org.whispersystems.libsignal.groups.state.SenderKeyRecord} 34 | * corresponding to the (groupId + senderId + deviceId) tuple, or a new SenderKeyRecord if 35 | * one does not currently exist. 36 | *

37 | * It is important that implementations return a copy of the current durable information. The 38 | * returned SenderKeyRecord may be modified, but those changes should not have an effect on the 39 | * durable session state (what is returned by subsequent calls to this method) without the 40 | * store method being called here first. 41 | * 42 | * @param senderKeyName The (groupId + senderId + deviceId) tuple. 43 | * @return a copy of the SenderKeyRecord corresponding to the (groupId + senderId + deviceId tuple, or 44 | * a new SenderKeyRecord if one does not currently exist. 45 | */ 46 | 47 | public SenderKeyRecord loadSenderKey(SenderKeyName senderKeyName); 48 | } 49 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SignedPreKeyRecord.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import com.google.protobuf.ByteString; 4 | 5 | import org.whispersystems.libsignal.InvalidKeyException; 6 | import org.whispersystems.libsignal.ecc.Curve; 7 | import org.whispersystems.libsignal.ecc.ECKeyPair; 8 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 9 | import org.whispersystems.libsignal.ecc.ECPublicKey; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.whispersystems.libsignal.state.StorageProtos.SignedPreKeyRecordStructure; 14 | 15 | public class SignedPreKeyRecord { 16 | 17 | private SignedPreKeyRecordStructure structure; 18 | 19 | public SignedPreKeyRecord(int id, long timestamp, ECKeyPair keyPair, byte[] signature) { 20 | this.structure = SignedPreKeyRecordStructure.newBuilder() 21 | .setId(id) 22 | .setPublicKey(ByteString.copyFrom(keyPair.getPublicKey() 23 | .serialize())) 24 | .setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey() 25 | .serialize())) 26 | .setSignature(ByteString.copyFrom(signature)) 27 | .setTimestamp(timestamp) 28 | .build(); 29 | } 30 | 31 | public SignedPreKeyRecord(byte[] serialized) throws IOException { 32 | this.structure = SignedPreKeyRecordStructure.parseFrom(serialized); 33 | } 34 | 35 | public int getId() { 36 | return this.structure.getId(); 37 | } 38 | 39 | public long getTimestamp() { 40 | return this.structure.getTimestamp(); 41 | } 42 | 43 | public ECKeyPair getKeyPair() { 44 | try { 45 | ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0); 46 | ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray()); 47 | 48 | return new ECKeyPair(publicKey, privateKey); 49 | } catch (InvalidKeyException e) { 50 | throw new AssertionError(e); 51 | } 52 | } 53 | 54 | public byte[] getSignature() { 55 | return this.structure.getSignature().toByteArray(); 56 | } 57 | 58 | public byte[] serialize() { 59 | return this.structure.toByteArray(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/IdentityKeyPair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal; 18 | 19 | import com.google.protobuf.ByteString; 20 | import com.google.protobuf.InvalidProtocolBufferException; 21 | 22 | import org.whispersystems.libsignal.ecc.Curve; 23 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 24 | 25 | import static org.whispersystems.libsignal.state.StorageProtos.IdentityKeyPairStructure; 26 | 27 | /** 28 | * Holder for public and private identity key pair. 29 | * 30 | * @author Moxie Marlinspike 31 | */ 32 | public class IdentityKeyPair { 33 | 34 | private final IdentityKey publicKey; 35 | private final ECPrivateKey privateKey; 36 | 37 | public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) { 38 | this.publicKey = publicKey; 39 | this.privateKey = privateKey; 40 | } 41 | 42 | public IdentityKeyPair(byte[] serialized) throws InvalidKeyException { 43 | try { 44 | IdentityKeyPairStructure structure = IdentityKeyPairStructure.parseFrom(serialized); 45 | this.publicKey = new IdentityKey(structure.getPublicKey().toByteArray(), 0); 46 | this.privateKey = Curve.decodePrivatePoint(structure.getPrivateKey().toByteArray()); 47 | } catch (InvalidProtocolBufferException e) { 48 | throw new InvalidKeyException(e); 49 | } 50 | } 51 | 52 | public IdentityKey getPublicKey() { 53 | return publicKey; 54 | } 55 | 56 | public ECPrivateKey getPrivateKey() { 57 | return privateKey; 58 | } 59 | 60 | public byte[] serialize() { 61 | return IdentityKeyPairStructure.newBuilder() 62 | .setPublicKey(ByteString.copyFrom(publicKey.serialize())) 63 | .setPrivateKey(ByteString.copyFrom(privateKey.serialize())) 64 | .build().toByteArray(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/Hex.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.util; 18 | 19 | import java.io.IOException; 20 | 21 | /** 22 | * Utility for generating hex dumps. 23 | */ 24 | public class Hex { 25 | 26 | private final static char[] HEX_DIGITS = { 27 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 28 | }; 29 | 30 | public static String toString(byte[] bytes) { 31 | return toString(bytes, 0, bytes.length); 32 | } 33 | 34 | public static String toString(byte[] bytes, int offset, int length) { 35 | StringBuffer buf = new StringBuffer(); 36 | for (int i = 0; i < length; i++) { 37 | appendHexChar(buf, bytes[offset + i]); 38 | buf.append(", "); 39 | } 40 | return buf.toString(); 41 | } 42 | 43 | public static String toStringCondensed(byte[] bytes) { 44 | StringBuffer buf = new StringBuffer(); 45 | for (int i=0;i> 1]; 60 | 61 | for (int i = 0, j = 0; j < len; i++) { 62 | int f = Character.digit(data[j], 16) << 4; 63 | j++; 64 | f = f | Character.digit(data[j], 16); 65 | j++; 66 | out[i] = (byte) (f & 0xFF); 67 | } 68 | 69 | return out; 70 | } 71 | 72 | private static void appendHexChar(StringBuffer buf, int b) { 73 | buf.append("(byte)0x"); 74 | buf.append(HEX_DIGITS[(b >> 4) & 0xf]); 75 | buf.append(HEX_DIGITS[b & 0xf]); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/guava/Function.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.whispersystems.libsignal.util.guava; 18 | 19 | 20 | 21 | /** 22 | * Determines an output value based on an input value. 23 | * 24 | *

See the Guava User Guide article on the use of {@code 26 | * Function}. 27 | * 28 | * @author Kevin Bourrillion 29 | * @since 2.0 (imported from Google Collections Library) 30 | */ 31 | 32 | public interface Function { 33 | /** 34 | * Returns the result of applying this function to {@code input}. This method is generally 35 | * expected, but not absolutely required, to have the following properties: 36 | * 37 | *

    38 | *
  • Its execution does not cause any observable side effects. 39 | *
  • The computation is consistent with equals; that is, {@link Objects#equal 40 | * Objects.equal}{@code (a, b)} implies that {@code Objects.equal(function.apply(a), 41 | * function.apply(b))}. 42 | *
43 | * 44 | * @throws NullPointerException if {@code input} is null and this function does not accept null 45 | * arguments 46 | */ 47 | T apply(F input); 48 | 49 | /** 50 | * Indicates whether another object is equal to this function. 51 | * 52 | *

Most implementations will have no reason to override the behavior of {@link Object#equals}. 53 | * However, an implementation may also choose to return {@code true} whenever {@code object} is a 54 | * {@link Function} that it considers interchangeable with this one. "Interchangeable" 55 | * typically means that {@code Objects.equal(this.apply(f), that.apply(f))} is true for all 56 | * {@code f} of type {@code F}. Note that a {@code false} result from this method does not imply 57 | * that the functions are known not to be interchangeable. 58 | */ 59 | @Override 60 | boolean equals(Object object); 61 | } 62 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/guava/Absent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.whispersystems.libsignal.util.guava; 18 | 19 | import static org.whispersystems.libsignal.util.guava.Preconditions.checkNotNull; 20 | 21 | 22 | 23 | import java.util.Collections; 24 | import java.util.Set; 25 | 26 | 27 | /** 28 | * Implementation of an {@link Optional} not containing a reference. 29 | */ 30 | 31 | final class Absent extends Optional { 32 | static final Absent INSTANCE = new Absent(); 33 | 34 | @Override public boolean isPresent() { 35 | return false; 36 | } 37 | 38 | @Override public Object get() { 39 | throw new IllegalStateException("value is absent"); 40 | } 41 | 42 | @Override public Object or(Object defaultValue) { 43 | return checkNotNull(defaultValue, "use orNull() instead of or(null)"); 44 | } 45 | 46 | @SuppressWarnings("unchecked") // safe covariant cast 47 | @Override public Optional or(Optional secondChoice) { 48 | return (Optional) checkNotNull(secondChoice); 49 | } 50 | 51 | @Override public Object or(Supplier supplier) { 52 | return checkNotNull(supplier.get(), 53 | "use orNull() instead of a Supplier that returns null"); 54 | } 55 | 56 | @Override public Object orNull() { 57 | return null; 58 | } 59 | 60 | @Override public Set asSet() { 61 | return Collections.emptySet(); 62 | } 63 | 64 | @Override 65 | public Optional transform(Function function) { 66 | checkNotNull(function); 67 | return Optional.absent(); 68 | } 69 | 70 | @Override public boolean equals(Object object) { 71 | return object == this; 72 | } 73 | 74 | @Override public int hashCode() { 75 | return 0x598df91c; 76 | } 77 | 78 | @Override public String toString() { 79 | return "Optional.absent()"; 80 | } 81 | 82 | private Object readResolve() { 83 | return INSTANCE; 84 | } 85 | 86 | private static final long serialVersionUID = 0; 87 | } 88 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SessionStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import org.whispersystems.libsignal.SignalProtocolAddress; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * The interface to the durable store of session state information 9 | * for remote clients. 10 | * 11 | * @author Moxie Marlinspike 12 | */ 13 | public interface SessionStore { 14 | 15 | /** 16 | * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, 17 | * or a new SessionRecord if one does not currently exist. 18 | *

19 | * It is important that implementations return a copy of the current durable information. The 20 | * returned SessionRecord may be modified, but those changes should not have an effect on the 21 | * durable session state (what is returned by subsequent calls to this method) without the 22 | * store method being called here first. 23 | * 24 | * @param address The name and device ID of the remote client. 25 | * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or 26 | * a new SessionRecord if one does not currently exist. 27 | */ 28 | public SessionRecord loadSession(SignalProtocolAddress address); 29 | 30 | /** 31 | * Returns all known devices with active sessions for a recipient 32 | * 33 | * @param name the name of the client. 34 | * @return all known sub-devices with active sessions. 35 | */ 36 | public List getSubDeviceSessions(String name); 37 | 38 | /** 39 | * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. 40 | * @param address the address of the remote client. 41 | * @param record the current SessionRecord for the remote client. 42 | */ 43 | public void storeSession(SignalProtocolAddress address, SessionRecord record); 44 | 45 | /** 46 | * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. 47 | * @param address the address of the remote client. 48 | * @return true if a {@link SessionRecord} exists, false otherwise. 49 | */ 50 | public boolean containsSession(SignalProtocolAddress address); 51 | 52 | /** 53 | * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. 54 | * 55 | * @param address the address of the remote client. 56 | */ 57 | public void deleteSession(SignalProtocolAddress address); 58 | 59 | /** 60 | * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. 61 | * 62 | * @param name the name of the remote client. 63 | */ 64 | public void deleteAllSessions(String name); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/ratchet/SenderChainKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.groups.ratchet; 18 | 19 | import java.security.InvalidKeyException; 20 | import java.security.NoSuchAlgorithmException; 21 | 22 | import javax.crypto.Mac; 23 | import javax.crypto.spec.SecretKeySpec; 24 | 25 | /** 26 | * Each SenderKey is a "chain" of keys, each derived from the previous. 27 | * 28 | * At any given point in time, the state of a SenderKey can be represented 29 | * as the current chain key value, along with its iteration count. From there, 30 | * subsequent iterations can be derived, as well as individual message keys from 31 | * each chain key. 32 | * 33 | * @author Moxie Marlinspike 34 | */ 35 | public class SenderChainKey { 36 | 37 | private static final byte[] MESSAGE_KEY_SEED = {0x01}; 38 | private static final byte[] CHAIN_KEY_SEED = {0x02}; 39 | 40 | private final int iteration; 41 | private final byte[] chainKey; 42 | 43 | public SenderChainKey(int iteration, byte[] chainKey) { 44 | this.iteration = iteration; 45 | this.chainKey = chainKey; 46 | } 47 | 48 | public int getIteration() { 49 | return iteration; 50 | } 51 | 52 | public SenderMessageKey getSenderMessageKey() { 53 | return new SenderMessageKey(iteration, getDerivative(MESSAGE_KEY_SEED, chainKey)); 54 | } 55 | 56 | public SenderChainKey getNext() { 57 | return new SenderChainKey(iteration + 1, getDerivative(CHAIN_KEY_SEED, chainKey)); 58 | } 59 | 60 | public byte[] getSeed() { 61 | return chainKey; 62 | } 63 | 64 | private byte[] getDerivative(byte[] seed, byte[] key) { 65 | try { 66 | Mac mac = Mac.getInstance("HmacSHA256"); 67 | mac.init(new SecretKeySpec(key, "HmacSHA256")); 68 | 69 | return mac.doFinal(seed); 70 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 71 | throw new AssertionError(e); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/guava/Present.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.whispersystems.libsignal.util.guava; 18 | 19 | import static org.whispersystems.libsignal.util.guava.Preconditions.checkNotNull; 20 | 21 | import java.util.Collections; 22 | import java.util.Set; 23 | 24 | /** 25 | * Implementation of an {@link Optional} containing a reference. 26 | */ 27 | 28 | final class Present extends Optional { 29 | private final T reference; 30 | 31 | Present(T reference) { 32 | this.reference = reference; 33 | } 34 | 35 | @Override public boolean isPresent() { 36 | return true; 37 | } 38 | 39 | @Override public T get() { 40 | return reference; 41 | } 42 | 43 | @Override public T or(T defaultValue) { 44 | checkNotNull(defaultValue, "use orNull() instead of or(null)"); 45 | return reference; 46 | } 47 | 48 | @Override public Optional or(Optional secondChoice) { 49 | checkNotNull(secondChoice); 50 | return this; 51 | } 52 | 53 | @Override public T or(Supplier supplier) { 54 | checkNotNull(supplier); 55 | return reference; 56 | } 57 | 58 | @Override public T orNull() { 59 | return reference; 60 | } 61 | 62 | @Override public Set asSet() { 63 | return Collections.singleton(reference); 64 | } 65 | 66 | @Override public Optional transform(Function function) { 67 | return new Present(checkNotNull(function.apply(reference), 68 | "Transformation function cannot return null.")); 69 | } 70 | 71 | @Override public boolean equals(Object object) { 72 | if (object instanceof Present) { 73 | Present other = (Present) object; 74 | return reference.equals(other.reference); 75 | } 76 | return false; 77 | } 78 | 79 | @Override public int hashCode() { 80 | return 0x598df91c + reference.hashCode(); 81 | } 82 | 83 | @Override public String toString() { 84 | return "Optional.of(" + reference + ")"; 85 | } 86 | 87 | private static final long serialVersionUID = 0; 88 | } 89 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/logging/Log.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.logging; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | import java.net.UnknownHostException; 6 | 7 | public class Log { 8 | 9 | private Log() {} 10 | 11 | public static void v(String tag, String msg) { 12 | log(SignalProtocolLogger.VERBOSE, tag, msg); 13 | } 14 | 15 | public static void v(String tag, String msg, Throwable tr) { 16 | log(SignalProtocolLogger.VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); 17 | } 18 | 19 | public static void d(String tag, String msg) { 20 | log(SignalProtocolLogger.DEBUG, tag, msg); 21 | } 22 | 23 | public static void d(String tag, String msg, Throwable tr) { 24 | log(SignalProtocolLogger.DEBUG, tag, msg + '\n' + getStackTraceString(tr)); 25 | } 26 | 27 | public static void i(String tag, String msg) { 28 | log(SignalProtocolLogger.INFO, tag, msg); 29 | } 30 | 31 | public static void i(String tag, String msg, Throwable tr) { 32 | log(SignalProtocolLogger.INFO, tag, msg + '\n' + getStackTraceString(tr)); 33 | } 34 | 35 | public static void w(String tag, String msg) { 36 | log(SignalProtocolLogger.WARN, tag, msg); 37 | } 38 | 39 | public static void w(String tag, String msg, Throwable tr) { 40 | log(SignalProtocolLogger.WARN, tag, msg + '\n' + getStackTraceString(tr)); 41 | } 42 | 43 | public static void w(String tag, Throwable tr) { 44 | log(SignalProtocolLogger.WARN, tag, getStackTraceString(tr)); 45 | } 46 | 47 | public static void e(String tag, String msg) { 48 | log(SignalProtocolLogger.ERROR, tag, msg); 49 | } 50 | 51 | public static void e(String tag, String msg, Throwable tr) { 52 | log(SignalProtocolLogger.ERROR, tag, msg + '\n' + getStackTraceString(tr)); 53 | } 54 | 55 | private static String getStackTraceString(Throwable tr) { 56 | if (tr == null) { 57 | return ""; 58 | } 59 | 60 | // This is to reduce the amount of log spew that apps do in the non-error 61 | // condition of the network being unavailable. 62 | Throwable t = tr; 63 | while (t != null) { 64 | if (t instanceof UnknownHostException) { 65 | return ""; 66 | } 67 | t = t.getCause(); 68 | } 69 | 70 | StringWriter sw = new StringWriter(); 71 | PrintWriter pw = new PrintWriter(sw); 72 | tr.printStackTrace(pw); 73 | pw.flush(); 74 | return sw.toString(); 75 | } 76 | 77 | private static void log(int priority, String tag, String msg) { 78 | SignalProtocolLogger logger = SignalProtocolLoggerProvider.getProvider(); 79 | 80 | if (logger != null) { 81 | logger.log(priority, tag, msg); 82 | } 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/ChainKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.ratchet; 18 | 19 | 20 | import org.whispersystems.libsignal.kdf.DerivedMessageSecrets; 21 | import org.whispersystems.libsignal.kdf.HKDF; 22 | 23 | import java.security.InvalidKeyException; 24 | import java.security.NoSuchAlgorithmException; 25 | 26 | import javax.crypto.Mac; 27 | import javax.crypto.spec.SecretKeySpec; 28 | 29 | public class ChainKey { 30 | 31 | private static final byte[] MESSAGE_KEY_SEED = {0x01}; 32 | private static final byte[] CHAIN_KEY_SEED = {0x02}; 33 | 34 | private final HKDF kdf; 35 | private final byte[] key; 36 | private final int index; 37 | 38 | public ChainKey(HKDF kdf, byte[] key, int index) { 39 | this.kdf = kdf; 40 | this.key = key; 41 | this.index = index; 42 | } 43 | 44 | public byte[] getKey() { 45 | return key; 46 | } 47 | 48 | public int getIndex() { 49 | return index; 50 | } 51 | 52 | public ChainKey getNextChainKey() { 53 | byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED); 54 | return new ChainKey(kdf, nextKey, index + 1); 55 | } 56 | 57 | public MessageKeys getMessageKeys() { 58 | byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED); 59 | byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE); 60 | DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes); 61 | 62 | return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), keyMaterial.getIv(), index); 63 | } 64 | 65 | private byte[] getBaseMaterial(byte[] seed) { 66 | try { 67 | Mac mac = Mac.getInstance("HmacSHA256"); 68 | mac.init(new SecretKeySpec(key, "HmacSHA256")); 69 | 70 | return mac.doFinal(seed); 71 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 72 | throw new AssertionError(e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/PreKeyBundle.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.ecc.ECPublicKey; 5 | 6 | /** 7 | * A class that contains a remote PreKey and collection 8 | * of associated items. 9 | * 10 | * @author Moxie Marlinspike 11 | */ 12 | public class PreKeyBundle { 13 | 14 | private int registrationId; 15 | 16 | private int deviceId; 17 | 18 | private int preKeyId; 19 | private ECPublicKey preKeyPublic; 20 | 21 | private int signedPreKeyId; 22 | private ECPublicKey signedPreKeyPublic; 23 | private byte[] signedPreKeySignature; 24 | 25 | private IdentityKey identityKey; 26 | 27 | public PreKeyBundle(int registrationId, int deviceId, int preKeyId, ECPublicKey preKeyPublic, 28 | int signedPreKeyId, ECPublicKey signedPreKeyPublic, byte[] signedPreKeySignature, 29 | IdentityKey identityKey) 30 | { 31 | this.registrationId = registrationId; 32 | this.deviceId = deviceId; 33 | this.preKeyId = preKeyId; 34 | this.preKeyPublic = preKeyPublic; 35 | this.signedPreKeyId = signedPreKeyId; 36 | this.signedPreKeyPublic = signedPreKeyPublic; 37 | this.signedPreKeySignature = signedPreKeySignature; 38 | this.identityKey = identityKey; 39 | } 40 | 41 | /** 42 | * @return the device ID this PreKey belongs to. 43 | */ 44 | public int getDeviceId() { 45 | return deviceId; 46 | } 47 | 48 | /** 49 | * @return the unique key ID for this PreKey. 50 | */ 51 | public int getPreKeyId() { 52 | return preKeyId; 53 | } 54 | 55 | /** 56 | * @return the public key for this PreKey. 57 | */ 58 | public ECPublicKey getPreKey() { 59 | return preKeyPublic; 60 | } 61 | 62 | /** 63 | * @return the unique key ID for this signed prekey. 64 | */ 65 | public int getSignedPreKeyId() { 66 | return signedPreKeyId; 67 | } 68 | 69 | /** 70 | * @return the signed prekey for this PreKeyBundle. 71 | */ 72 | public ECPublicKey getSignedPreKey() { 73 | return signedPreKeyPublic; 74 | } 75 | 76 | /** 77 | * @return the signature over the signed prekey. 78 | */ 79 | public byte[] getSignedPreKeySignature() { 80 | return signedPreKeySignature; 81 | } 82 | 83 | /** 84 | * @return the {@link org.whispersystems.libsignal.IdentityKey} of this PreKeys owner. 85 | */ 86 | public IdentityKey getIdentityKey() { 87 | return identityKey; 88 | } 89 | 90 | /** 91 | * @return the registration ID associated with this PreKey. 92 | */ 93 | public int getRegistrationId() { 94 | return registrationId; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | apply plugin: 'signing' 4 | 5 | sourceCompatibility = 1.7 6 | archivesBaseName = "signal-protocol-java" 7 | version = version_number 8 | group = group_info 9 | 10 | repositories { 11 | mavenCentral() 12 | mavenLocal() 13 | } 14 | 15 | sourceSets { 16 | test { 17 | java { 18 | srcDirs = ['src/test/java/', project(':tests').file('src/test/java')] 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile "org.whispersystems:curve25519-java:${curve25519_version}" 25 | compile 'com.google.protobuf:protobuf-java:2.5.0' 26 | 27 | testCompile ('junit:junit:3.8.2') 28 | } 29 | 30 | 31 | test { 32 | testLogging { 33 | events 'passed' 34 | showStandardStreams = true 35 | } 36 | 37 | include 'org/whispersystems/**' 38 | } 39 | 40 | signing { 41 | required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } 42 | sign configurations.archives 43 | } 44 | 45 | uploadArchives { 46 | configuration = configurations.archives 47 | repositories.mavenDeployer { 48 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 49 | 50 | repository(url: sonatypeRepo) { 51 | authentication(userName: whisperSonatypeUsername, password: whisperSonatypePassword) 52 | } 53 | 54 | pom.project { 55 | name 'signal-protocol-java' 56 | packaging 'jar' 57 | description 'Signal Protocol cryptography library for Java' 58 | url 'https://github.com/WhisperSystems/libsignal-android' 59 | 60 | scm { 61 | url 'scm:git@github.com:WhisperSystems/libsignal-android.git' 62 | connection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 63 | developerConnection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 64 | } 65 | 66 | licenses { 67 | license { 68 | name 'GPLv3' 69 | url 'https://www.gnu.org/licenses/gpl-3.0.txt' 70 | distribution 'repo' 71 | } 72 | } 73 | 74 | developers { 75 | developer { 76 | name 'Moxie Marlinspike' 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | task installArchives(type: Upload) { 84 | description "Installs the artifacts to the local Maven repository." 85 | configuration = configurations['archives'] 86 | repositories { 87 | mavenDeployer { 88 | repository url: "file://${System.properties['user.home']}/.m2/repository" 89 | } 90 | } 91 | } 92 | 93 | task packageJavadoc(type: Jar, dependsOn: 'javadoc') { 94 | from javadoc.destinationDir 95 | classifier = 'javadoc' 96 | } 97 | 98 | task packageSources(type: Jar) { 99 | from sourceSets.main.allSource 100 | classifier = 'sources' 101 | } 102 | 103 | artifacts { 104 | archives(packageJavadoc) { 105 | type = 'javadoc' 106 | } 107 | 108 | archives packageSources 109 | } 110 | 111 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.0.0' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | apply plugin: 'maven' 13 | apply plugin: 'signing' 14 | 15 | archivesBaseName = "signal-protocol-android" 16 | version = version_number 17 | group = group_info 18 | 19 | android { 20 | compileSdkVersion 21 21 | buildToolsVersion "21.1.2" 22 | 23 | sourceSets { 24 | androidTest { 25 | java.srcDirs = ['src/androidTest/java', project(':tests').file('src/test/java')] 26 | } 27 | } 28 | 29 | libraryVariants.all { variant -> 30 | variant.outputs.each { output -> 31 | def outputFile = output.outputFile 32 | if (outputFile != null && outputFile.name.endsWith('.aar')) { 33 | def fileName = "${archivesBaseName}-${version}.aar" 34 | output.outputFile = new File(outputFile.parent, fileName) 35 | } 36 | } 37 | } 38 | } 39 | 40 | repositories { 41 | mavenCentral() 42 | mavenLocal() 43 | } 44 | 45 | dependencies { 46 | compile "org.whispersystems:curve25519-android:${curve25519_version}" 47 | compile (project(':java')) { 48 | exclude group: 'org.whispersystems', module: 'curve25519-java' 49 | } 50 | } 51 | 52 | signing { 53 | required { has("release") && gradle.taskGraph.hasTask("uploadArchives") } 54 | sign configurations.archives 55 | } 56 | 57 | uploadArchives { 58 | configuration = configurations.archives 59 | repositories.mavenDeployer { 60 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 61 | 62 | repository(url: sonatypeRepo) { 63 | authentication(userName: whisperSonatypeUsername, password: whisperSonatypePassword) 64 | } 65 | 66 | pom.project { 67 | name 'signal-protocol-android' 68 | packaging 'aar' 69 | description 'Signal Protocol cryptography library for Android' 70 | url 'https://github.com/WhisperSystems/libsignal-android' 71 | 72 | scm { 73 | url 'scm:git@github.com:WhisperSystems/libsignal-android.git' 74 | connection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 75 | developerConnection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 76 | } 77 | 78 | licenses { 79 | license { 80 | name 'GPLv3' 81 | url 'https://www.gnu.org/licenses/gpl-3.0.txt' 82 | distribution 'repo' 83 | } 84 | } 85 | 86 | developers { 87 | developer { 88 | name 'Moxie Marlinspike' 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | task installArchives(type: Upload) { 96 | description "Installs the artifacts to the local Maven repository." 97 | configuration = configurations['archives'] 98 | repositories { 99 | mavenDeployer { 100 | repository url: "file://${System.properties['user.home']}/.m2/repository" 101 | } 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/HKDF.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.whispersystems.libsignal.kdf; 19 | 20 | import java.io.ByteArrayOutputStream; 21 | import java.security.InvalidKeyException; 22 | import java.security.NoSuchAlgorithmException; 23 | 24 | import javax.crypto.Mac; 25 | import javax.crypto.spec.SecretKeySpec; 26 | 27 | public abstract class HKDF { 28 | 29 | private static final int HASH_OUTPUT_SIZE = 32; 30 | 31 | public static HKDF createFor(int messageVersion) { 32 | switch (messageVersion) { 33 | case 2: return new HKDFv2(); 34 | case 3: return new HKDFv3(); 35 | default: throw new AssertionError("Unknown version: " + messageVersion); 36 | } 37 | } 38 | 39 | public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] info, int outputLength) { 40 | byte[] salt = new byte[HASH_OUTPUT_SIZE]; 41 | return deriveSecrets(inputKeyMaterial, salt, info, outputLength); 42 | } 43 | 44 | public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info, int outputLength) { 45 | byte[] prk = extract(salt, inputKeyMaterial); 46 | return expand(prk, info, outputLength); 47 | } 48 | 49 | private byte[] extract(byte[] salt, byte[] inputKeyMaterial) { 50 | try { 51 | Mac mac = Mac.getInstance("HmacSHA256"); 52 | mac.init(new SecretKeySpec(salt, "HmacSHA256")); 53 | return mac.doFinal(inputKeyMaterial); 54 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 55 | throw new AssertionError(e); 56 | } 57 | } 58 | 59 | private byte[] expand(byte[] prk, byte[] info, int outputSize) { 60 | try { 61 | int iterations = (int) Math.ceil((double) outputSize / (double) HASH_OUTPUT_SIZE); 62 | byte[] mixin = new byte[0]; 63 | ByteArrayOutputStream results = new ByteArrayOutputStream(); 64 | int remainingBytes = outputSize; 65 | 66 | for (int i= getIterationStartOffset();i. 16 | */ 17 | package org.whispersystems.libsignal.groups.state; 18 | 19 | import org.whispersystems.libsignal.InvalidKeyIdException; 20 | import org.whispersystems.libsignal.ecc.ECKeyPair; 21 | import org.whispersystems.libsignal.ecc.ECPublicKey; 22 | import org.whispersystems.libsignal.state.StorageProtos; 23 | 24 | import java.io.IOException; 25 | import java.util.LinkedList; 26 | import java.util.List; 27 | 28 | import static org.whispersystems.libsignal.state.StorageProtos.SenderKeyRecordStructure; 29 | 30 | /** 31 | * A durable representation of a set of SenderKeyStates for a specific 32 | * SenderKeyName. 33 | * 34 | * @author Moxie Marlisnpike 35 | */ 36 | public class SenderKeyRecord { 37 | 38 | private static final int MAX_STATES = 5; 39 | 40 | private LinkedList senderKeyStates = new LinkedList<>(); 41 | 42 | public SenderKeyRecord() {} 43 | 44 | public SenderKeyRecord(byte[] serialized) throws IOException { 45 | SenderKeyRecordStructure senderKeyRecordStructure = SenderKeyRecordStructure.parseFrom(serialized); 46 | 47 | for (StorageProtos.SenderKeyStateStructure structure : senderKeyRecordStructure.getSenderKeyStatesList()) { 48 | this.senderKeyStates.add(new SenderKeyState(structure)); 49 | } 50 | } 51 | 52 | public boolean isEmpty() { 53 | return senderKeyStates.isEmpty(); 54 | } 55 | 56 | public SenderKeyState getSenderKeyState() throws InvalidKeyIdException { 57 | if (!senderKeyStates.isEmpty()) { 58 | return senderKeyStates.get(0); 59 | } else { 60 | throw new InvalidKeyIdException("No key state in record!"); 61 | } 62 | } 63 | 64 | public SenderKeyState getSenderKeyState(int keyId) throws InvalidKeyIdException { 65 | for (SenderKeyState state : senderKeyStates) { 66 | if (state.getKeyId() == keyId) { 67 | return state; 68 | } 69 | } 70 | 71 | throw new InvalidKeyIdException("No keys for: " + keyId); 72 | } 73 | 74 | public void addSenderKeyState(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { 75 | senderKeyStates.addFirst(new SenderKeyState(id, iteration, chainKey, signatureKey)); 76 | 77 | if (senderKeyStates.size() > MAX_STATES) { 78 | senderKeyStates.removeLast(); 79 | } 80 | } 81 | 82 | public void setSenderKeyState(int id, int iteration, byte[] chainKey, ECKeyPair signatureKey) { 83 | senderKeyStates.clear(); 84 | senderKeyStates.add(new SenderKeyState(id, iteration, chainKey, signatureKey)); 85 | } 86 | 87 | public byte[] serialize() { 88 | SenderKeyRecordStructure.Builder recordStructure = SenderKeyRecordStructure.newBuilder(); 89 | 90 | for (SenderKeyState senderKeyState : senderKeyStates) { 91 | recordStructure.addSenderKeyStates(senderKeyState.getStructure()); 92 | } 93 | 94 | return recordStructure.build().toByteArray(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGenerator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.util.ByteUtil; 5 | 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | 9 | public class NumericFingerprintGenerator implements FingerprintGenerator { 10 | 11 | private static final int VERSION = 0; 12 | 13 | private final long iterations; 14 | 15 | /** 16 | * Construct a fingerprint generator for 60 digit numerics. 17 | * 18 | * @param iterations The number of internal iterations to perform in the process of 19 | * generating a fingerprint. This needs to be constant, and synchronized 20 | * across all clients. 21 | * 22 | * The higher the iteration count, the higher the security level: 23 | * 24 | * - 1024 ~ 109.7 bits 25 | * - 1400 > 110 bits 26 | * - 5200 > 112 bits 27 | */ 28 | public NumericFingerprintGenerator(long iterations) { 29 | this.iterations = iterations; 30 | } 31 | 32 | /** 33 | * Generate a scannable and displayble fingerprint. 34 | * 35 | * @param localStableIdentifier The client's "stable" identifier. 36 | * @param localIdentityKey The client's identity key. 37 | * @param remoteStableIdentifier The remote party's "stable" identifier. 38 | * @param remoteIdentityKey The remote party's identity key. 39 | * @return A unique fingerprint for this conversation. 40 | */ 41 | @Override 42 | public Fingerprint createFor(String localStableIdentifier, IdentityKey localIdentityKey, 43 | String remoteStableIdentifier, IdentityKey remoteIdentityKey) 44 | { 45 | DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(getDisplayStringFor(localStableIdentifier, localIdentityKey), 46 | getDisplayStringFor(remoteStableIdentifier, remoteIdentityKey)); 47 | 48 | ScannableFingerprint scannableFingerprint = new ScannableFingerprint(VERSION, 49 | localStableIdentifier, localIdentityKey, 50 | remoteStableIdentifier, remoteIdentityKey); 51 | 52 | return new Fingerprint(displayableFingerprint, scannableFingerprint); 53 | } 54 | 55 | private String getDisplayStringFor(String stableIdentifier, IdentityKey identityKey) { 56 | try { 57 | MessageDigest digest = MessageDigest.getInstance("SHA-512"); 58 | byte[] publicKey = identityKey.getPublicKey().serialize(); 59 | byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(VERSION), 60 | publicKey, stableIdentifier.getBytes()); 61 | 62 | for (int i=0;i. 16 | */ 17 | package org.whispersystems.libsignal.ecc; 18 | 19 | import org.whispersystems.curve25519.Curve25519; 20 | import org.whispersystems.curve25519.Curve25519KeyPair; 21 | import org.whispersystems.curve25519.NoSuchProviderException; 22 | import org.whispersystems.libsignal.InvalidKeyException; 23 | 24 | import static org.whispersystems.curve25519.Curve25519.BEST; 25 | 26 | public class Curve { 27 | 28 | public static final int DJB_TYPE = 0x05; 29 | 30 | public static boolean isNative() { 31 | return Curve25519.getInstance(BEST).isNative(); 32 | } 33 | 34 | public static ECKeyPair generateKeyPair() { 35 | Curve25519KeyPair keyPair = Curve25519.getInstance(BEST).generateKeyPair(); 36 | 37 | return new ECKeyPair(new DjbECPublicKey(keyPair.getPublicKey()), 38 | new DjbECPrivateKey(keyPair.getPrivateKey())); 39 | } 40 | 41 | public static ECPublicKey decodePoint(byte[] bytes, int offset) 42 | throws InvalidKeyException 43 | { 44 | int type = bytes[offset] & 0xFF; 45 | 46 | switch (type) { 47 | case Curve.DJB_TYPE: 48 | byte[] keyBytes = new byte[32]; 49 | System.arraycopy(bytes, offset+1, keyBytes, 0, keyBytes.length); 50 | return new DjbECPublicKey(keyBytes); 51 | default: 52 | throw new InvalidKeyException("Bad key type: " + type); 53 | } 54 | } 55 | 56 | public static ECPrivateKey decodePrivatePoint(byte[] bytes) { 57 | return new DjbECPrivateKey(bytes); 58 | } 59 | 60 | public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) 61 | throws InvalidKeyException 62 | { 63 | if (publicKey.getType() != privateKey.getType()) { 64 | throw new InvalidKeyException("Public and private keys must be of the same type!"); 65 | } 66 | 67 | if (publicKey.getType() == DJB_TYPE) { 68 | return Curve25519.getInstance(BEST) 69 | .calculateAgreement(((DjbECPublicKey) publicKey).getPublicKey(), 70 | ((DjbECPrivateKey) privateKey).getPrivateKey()); 71 | } else { 72 | throw new InvalidKeyException("Unknown type: " + publicKey.getType()); 73 | } 74 | } 75 | 76 | public static boolean verifySignature(ECPublicKey signingKey, byte[] message, byte[] signature) 77 | throws InvalidKeyException 78 | { 79 | if (signingKey.getType() == DJB_TYPE) { 80 | return Curve25519.getInstance(BEST) 81 | .verifySignature(((DjbECPublicKey) signingKey).getPublicKey(), message, signature); 82 | } else { 83 | throw new InvalidKeyException("Unknown type: " + signingKey.getType()); 84 | } 85 | } 86 | 87 | public static byte[] calculateSignature(ECPrivateKey signingKey, byte[] message) 88 | throws InvalidKeyException 89 | { 90 | if (signingKey.getType() == DJB_TYPE) { 91 | return Curve25519.getInstance(BEST) 92 | .calculateSignature(((DjbECPrivateKey) signingKey).getPrivateKey(), message); 93 | } else { 94 | throw new InvalidKeyException("Unknown type: " + signingKey.getType()); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SessionRecord.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | import static org.whispersystems.libsignal.state.StorageProtos.RecordStructure; 9 | import static org.whispersystems.libsignal.state.StorageProtos.SessionStructure; 10 | 11 | /** 12 | * A SessionRecord encapsulates the state of an ongoing session. 13 | * 14 | * @author Moxie Marlinspike 15 | */ 16 | public class SessionRecord { 17 | 18 | private static final int ARCHIVED_STATES_MAX_LENGTH = 40; 19 | 20 | private SessionState sessionState = new SessionState(); 21 | private LinkedList previousStates = new LinkedList<>(); 22 | private boolean fresh = false; 23 | 24 | public SessionRecord() { 25 | this.fresh = true; 26 | } 27 | 28 | public SessionRecord(SessionState sessionState) { 29 | this.sessionState = sessionState; 30 | this.fresh = false; 31 | } 32 | 33 | public SessionRecord(byte[] serialized) throws IOException { 34 | RecordStructure record = RecordStructure.parseFrom(serialized); 35 | this.sessionState = new SessionState(record.getCurrentSession()); 36 | this.fresh = false; 37 | 38 | for (SessionStructure previousStructure : record.getPreviousSessionsList()) { 39 | previousStates.add(new SessionState(previousStructure)); 40 | } 41 | } 42 | 43 | public boolean hasSessionState(int version, byte[] aliceBaseKey) { 44 | if (sessionState.getSessionVersion() == version && 45 | Arrays.equals(aliceBaseKey, sessionState.getAliceBaseKey())) 46 | { 47 | return true; 48 | } 49 | 50 | for (SessionState state : previousStates) { 51 | if (state.getSessionVersion() == version && 52 | Arrays.equals(aliceBaseKey, state.getAliceBaseKey())) 53 | { 54 | return true; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | 61 | public SessionState getSessionState() { 62 | return sessionState; 63 | } 64 | 65 | /** 66 | * @return the list of all currently maintained "previous" session states. 67 | */ 68 | public List getPreviousSessionStates() { 69 | return previousStates; 70 | } 71 | 72 | 73 | public boolean isFresh() { 74 | return fresh; 75 | } 76 | 77 | /** 78 | * Move the current {@link SessionState} into the list of "previous" session states, 79 | * and replace the current {@link org.whispersystems.libsignal.state.SessionState} 80 | * with a fresh reset instance. 81 | */ 82 | public void archiveCurrentState() { 83 | promoteState(new SessionState()); 84 | } 85 | 86 | public void promoteState(SessionState promotedState) { 87 | this.previousStates.addFirst(sessionState); 88 | this.sessionState = promotedState; 89 | 90 | if (previousStates.size() > ARCHIVED_STATES_MAX_LENGTH) { 91 | previousStates.removeLast(); 92 | } 93 | } 94 | 95 | public void setState(SessionState sessionState) { 96 | this.sessionState = sessionState; 97 | } 98 | 99 | /** 100 | * @return a serialized version of the current SessionRecord. 101 | */ 102 | public byte[] serialize() { 103 | List previousStructures = new LinkedList<>(); 104 | 105 | for (SessionState previousState : previousStates) { 106 | previousStructures.add(previousState.getStructure()); 107 | } 108 | 109 | RecordStructure record = RecordStructure.newBuilder() 110 | .setCurrentSession(sessionState.getStructure()) 111 | .addAllPreviousSessions(previousStructures) 112 | .build(); 113 | 114 | return record.toByteArray(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/BobSignalProtocolParameters.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.ratchet; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.IdentityKeyPair; 5 | import org.whispersystems.libsignal.ecc.ECKeyPair; 6 | import org.whispersystems.libsignal.ecc.ECPublicKey; 7 | import org.whispersystems.libsignal.util.guava.Optional; 8 | 9 | public class BobSignalProtocolParameters { 10 | 11 | private final IdentityKeyPair ourIdentityKey; 12 | private final ECKeyPair ourSignedPreKey; 13 | private final Optional ourOneTimePreKey; 14 | private final ECKeyPair ourRatchetKey; 15 | 16 | private final IdentityKey theirIdentityKey; 17 | private final ECPublicKey theirBaseKey; 18 | 19 | BobSignalProtocolParameters(IdentityKeyPair ourIdentityKey, ECKeyPair ourSignedPreKey, 20 | ECKeyPair ourRatchetKey, Optional ourOneTimePreKey, 21 | IdentityKey theirIdentityKey, ECPublicKey theirBaseKey) 22 | { 23 | this.ourIdentityKey = ourIdentityKey; 24 | this.ourSignedPreKey = ourSignedPreKey; 25 | this.ourRatchetKey = ourRatchetKey; 26 | this.ourOneTimePreKey = ourOneTimePreKey; 27 | this.theirIdentityKey = theirIdentityKey; 28 | this.theirBaseKey = theirBaseKey; 29 | 30 | if (ourIdentityKey == null || ourSignedPreKey == null || ourRatchetKey == null || 31 | ourOneTimePreKey == null || theirIdentityKey == null || theirBaseKey == null) 32 | { 33 | throw new IllegalArgumentException("Null value!"); 34 | } 35 | } 36 | 37 | public IdentityKeyPair getOurIdentityKey() { 38 | return ourIdentityKey; 39 | } 40 | 41 | public ECKeyPair getOurSignedPreKey() { 42 | return ourSignedPreKey; 43 | } 44 | 45 | public Optional getOurOneTimePreKey() { 46 | return ourOneTimePreKey; 47 | } 48 | 49 | public IdentityKey getTheirIdentityKey() { 50 | return theirIdentityKey; 51 | } 52 | 53 | public ECPublicKey getTheirBaseKey() { 54 | return theirBaseKey; 55 | } 56 | 57 | public static Builder newBuilder() { 58 | return new Builder(); 59 | } 60 | 61 | public ECKeyPair getOurRatchetKey() { 62 | return ourRatchetKey; 63 | } 64 | 65 | public static class Builder { 66 | private IdentityKeyPair ourIdentityKey; 67 | private ECKeyPair ourSignedPreKey; 68 | private Optional ourOneTimePreKey; 69 | private ECKeyPair ourRatchetKey; 70 | 71 | private IdentityKey theirIdentityKey; 72 | private ECPublicKey theirBaseKey; 73 | 74 | public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) { 75 | this.ourIdentityKey = ourIdentityKey; 76 | return this; 77 | } 78 | 79 | public Builder setOurSignedPreKey(ECKeyPair ourSignedPreKey) { 80 | this.ourSignedPreKey = ourSignedPreKey; 81 | return this; 82 | } 83 | 84 | public Builder setOurOneTimePreKey(Optional ourOneTimePreKey) { 85 | this.ourOneTimePreKey = ourOneTimePreKey; 86 | return this; 87 | } 88 | 89 | public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) { 90 | this.theirIdentityKey = theirIdentityKey; 91 | return this; 92 | } 93 | 94 | public Builder setTheirBaseKey(ECPublicKey theirBaseKey) { 95 | this.theirBaseKey = theirBaseKey; 96 | return this; 97 | } 98 | 99 | public Builder setOurRatchetKey(ECKeyPair ourRatchetKey) { 100 | this.ourRatchetKey = ourRatchetKey; 101 | return this; 102 | } 103 | 104 | public BobSignalProtocolParameters create() { 105 | return new BobSignalProtocolParameters(ourIdentityKey, ourSignedPreKey, ourRatchetKey, 106 | ourOneTimePreKey, theirIdentityKey, theirBaseKey); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/SenderKeyDistributionMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.protocol; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | 6 | import org.whispersystems.libsignal.InvalidKeyException; 7 | import org.whispersystems.libsignal.InvalidMessageException; 8 | import org.whispersystems.libsignal.LegacyMessageException; 9 | import org.whispersystems.libsignal.ecc.Curve; 10 | import org.whispersystems.libsignal.ecc.ECPublicKey; 11 | import org.whispersystems.libsignal.util.ByteUtil; 12 | 13 | public class SenderKeyDistributionMessage implements CiphertextMessage { 14 | 15 | private final int id; 16 | private final int iteration; 17 | private final byte[] chainKey; 18 | private final ECPublicKey signatureKey; 19 | private final byte[] serialized; 20 | 21 | public SenderKeyDistributionMessage(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { 22 | byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)}; 23 | byte[] protobuf = SignalProtos.SenderKeyDistributionMessage.newBuilder() 24 | .setId(id) 25 | .setIteration(iteration) 26 | .setChainKey(ByteString.copyFrom(chainKey)) 27 | .setSigningKey(ByteString.copyFrom(signatureKey.serialize())) 28 | .build().toByteArray(); 29 | 30 | this.id = id; 31 | this.iteration = iteration; 32 | this.chainKey = chainKey; 33 | this.signatureKey = signatureKey; 34 | this.serialized = ByteUtil.combine(version, protobuf); 35 | } 36 | 37 | public SenderKeyDistributionMessage(byte[] serialized) throws LegacyMessageException, InvalidMessageException { 38 | try { 39 | byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1); 40 | byte version = messageParts[0][0]; 41 | byte[] message = messageParts[1]; 42 | 43 | if (ByteUtil.highBitsToInt(version) < CiphertextMessage.CURRENT_VERSION) { 44 | throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); 45 | } 46 | 47 | if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { 48 | throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); 49 | } 50 | 51 | SignalProtos.SenderKeyDistributionMessage distributionMessage = SignalProtos.SenderKeyDistributionMessage.parseFrom(message); 52 | 53 | if (!distributionMessage.hasId() || 54 | !distributionMessage.hasIteration() || 55 | !distributionMessage.hasChainKey() || 56 | !distributionMessage.hasSigningKey()) 57 | { 58 | throw new InvalidMessageException("Incomplete message."); 59 | } 60 | 61 | this.serialized = serialized; 62 | this.id = distributionMessage.getId(); 63 | this.iteration = distributionMessage.getIteration(); 64 | this.chainKey = distributionMessage.getChainKey().toByteArray(); 65 | this.signatureKey = Curve.decodePoint(distributionMessage.getSigningKey().toByteArray(), 0); 66 | } catch (InvalidProtocolBufferException | InvalidKeyException e) { 67 | throw new InvalidMessageException(e); 68 | } 69 | } 70 | 71 | @Override 72 | public byte[] serialize() { 73 | return serialized; 74 | } 75 | 76 | @Override 77 | public int getType() { 78 | return SENDERKEY_DISTRIBUTION_TYPE; 79 | } 80 | 81 | public int getIteration() { 82 | return iteration; 83 | } 84 | 85 | public byte[] getChainKey() { 86 | return chainKey; 87 | } 88 | 89 | public ECPublicKey getSignatureKey() { 90 | return signatureKey; 91 | } 92 | 93 | public int getId() { 94 | return id; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/AliceSignalProtocolParameters.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.ratchet; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.IdentityKeyPair; 5 | import org.whispersystems.libsignal.ecc.ECKeyPair; 6 | import org.whispersystems.libsignal.ecc.ECPublicKey; 7 | import org.whispersystems.libsignal.util.guava.Optional; 8 | 9 | public class AliceSignalProtocolParameters { 10 | 11 | private final IdentityKeyPair ourIdentityKey; 12 | private final ECKeyPair ourBaseKey; 13 | 14 | private final IdentityKey theirIdentityKey; 15 | private final ECPublicKey theirSignedPreKey; 16 | private final Optional theirOneTimePreKey; 17 | private final ECPublicKey theirRatchetKey; 18 | 19 | private AliceSignalProtocolParameters(IdentityKeyPair ourIdentityKey, ECKeyPair ourBaseKey, 20 | IdentityKey theirIdentityKey, ECPublicKey theirSignedPreKey, 21 | ECPublicKey theirRatchetKey, Optional theirOneTimePreKey) 22 | { 23 | this.ourIdentityKey = ourIdentityKey; 24 | this.ourBaseKey = ourBaseKey; 25 | this.theirIdentityKey = theirIdentityKey; 26 | this.theirSignedPreKey = theirSignedPreKey; 27 | this.theirRatchetKey = theirRatchetKey; 28 | this.theirOneTimePreKey = theirOneTimePreKey; 29 | 30 | if (ourIdentityKey == null || ourBaseKey == null || theirIdentityKey == null || 31 | theirSignedPreKey == null || theirRatchetKey == null || theirOneTimePreKey == null) 32 | { 33 | throw new IllegalArgumentException("Null values!"); 34 | } 35 | } 36 | 37 | public IdentityKeyPair getOurIdentityKey() { 38 | return ourIdentityKey; 39 | } 40 | 41 | public ECKeyPair getOurBaseKey() { 42 | return ourBaseKey; 43 | } 44 | 45 | public IdentityKey getTheirIdentityKey() { 46 | return theirIdentityKey; 47 | } 48 | 49 | public ECPublicKey getTheirSignedPreKey() { 50 | return theirSignedPreKey; 51 | } 52 | 53 | public Optional getTheirOneTimePreKey() { 54 | return theirOneTimePreKey; 55 | } 56 | 57 | public static Builder newBuilder() { 58 | return new Builder(); 59 | } 60 | 61 | public ECPublicKey getTheirRatchetKey() { 62 | return theirRatchetKey; 63 | } 64 | 65 | public static class Builder { 66 | private IdentityKeyPair ourIdentityKey; 67 | private ECKeyPair ourBaseKey; 68 | 69 | private IdentityKey theirIdentityKey; 70 | private ECPublicKey theirSignedPreKey; 71 | private ECPublicKey theirRatchetKey; 72 | private Optional theirOneTimePreKey; 73 | 74 | public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) { 75 | this.ourIdentityKey = ourIdentityKey; 76 | return this; 77 | } 78 | 79 | public Builder setOurBaseKey(ECKeyPair ourBaseKey) { 80 | this.ourBaseKey = ourBaseKey; 81 | return this; 82 | } 83 | 84 | public Builder setTheirRatchetKey(ECPublicKey theirRatchetKey) { 85 | this.theirRatchetKey = theirRatchetKey; 86 | return this; 87 | } 88 | 89 | public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) { 90 | this.theirIdentityKey = theirIdentityKey; 91 | return this; 92 | } 93 | 94 | public Builder setTheirSignedPreKey(ECPublicKey theirSignedPreKey) { 95 | this.theirSignedPreKey = theirSignedPreKey; 96 | return this; 97 | } 98 | 99 | public Builder setTheirOneTimePreKey(Optional theirOneTimePreKey) { 100 | this.theirOneTimePreKey = theirOneTimePreKey; 101 | return this; 102 | } 103 | 104 | public AliceSignalProtocolParameters create() { 105 | return new AliceSignalProtocolParameters(ourIdentityKey, ourBaseKey, theirIdentityKey, 106 | theirSignedPreKey, theirRatchetKey, theirOneTimePreKey); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemorySignalProtocolStore.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.state.impl; 2 | 3 | import org.whispersystems.libsignal.SignalProtocolAddress; 4 | import org.whispersystems.libsignal.IdentityKey; 5 | import org.whispersystems.libsignal.IdentityKeyPair; 6 | import org.whispersystems.libsignal.InvalidKeyIdException; 7 | import org.whispersystems.libsignal.state.SignalProtocolStore; 8 | import org.whispersystems.libsignal.state.PreKeyRecord; 9 | import org.whispersystems.libsignal.state.SessionRecord; 10 | import org.whispersystems.libsignal.state.SignedPreKeyRecord; 11 | 12 | import java.util.List; 13 | 14 | public class InMemorySignalProtocolStore implements SignalProtocolStore { 15 | 16 | private final InMemoryPreKeyStore preKeyStore = new InMemoryPreKeyStore(); 17 | private final InMemorySessionStore sessionStore = new InMemorySessionStore(); 18 | private final InMemorySignedPreKeyStore signedPreKeyStore = new InMemorySignedPreKeyStore(); 19 | 20 | private final InMemoryIdentityKeyStore identityKeyStore; 21 | 22 | public InMemorySignalProtocolStore(IdentityKeyPair identityKeyPair, int registrationId) { 23 | this.identityKeyStore = new InMemoryIdentityKeyStore(identityKeyPair, registrationId); 24 | } 25 | 26 | @Override 27 | public IdentityKeyPair getIdentityKeyPair() { 28 | return identityKeyStore.getIdentityKeyPair(); 29 | } 30 | 31 | @Override 32 | public int getLocalRegistrationId() { 33 | return identityKeyStore.getLocalRegistrationId(); 34 | } 35 | 36 | @Override 37 | public void saveIdentity(String name, IdentityKey identityKey) { 38 | identityKeyStore.saveIdentity(name, identityKey); 39 | } 40 | 41 | @Override 42 | public boolean isTrustedIdentity(String name, IdentityKey identityKey) { 43 | return identityKeyStore.isTrustedIdentity(name, identityKey); 44 | } 45 | 46 | @Override 47 | public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { 48 | return preKeyStore.loadPreKey(preKeyId); 49 | } 50 | 51 | @Override 52 | public void storePreKey(int preKeyId, PreKeyRecord record) { 53 | preKeyStore.storePreKey(preKeyId, record); 54 | } 55 | 56 | @Override 57 | public boolean containsPreKey(int preKeyId) { 58 | return preKeyStore.containsPreKey(preKeyId); 59 | } 60 | 61 | @Override 62 | public void removePreKey(int preKeyId) { 63 | preKeyStore.removePreKey(preKeyId); 64 | } 65 | 66 | @Override 67 | public SessionRecord loadSession(SignalProtocolAddress address) { 68 | return sessionStore.loadSession(address); 69 | } 70 | 71 | @Override 72 | public List getSubDeviceSessions(String name) { 73 | return sessionStore.getSubDeviceSessions(name); 74 | } 75 | 76 | @Override 77 | public void storeSession(SignalProtocolAddress address, SessionRecord record) { 78 | sessionStore.storeSession(address, record); 79 | } 80 | 81 | @Override 82 | public boolean containsSession(SignalProtocolAddress address) { 83 | return sessionStore.containsSession(address); 84 | } 85 | 86 | @Override 87 | public void deleteSession(SignalProtocolAddress address) { 88 | sessionStore.deleteSession(address); 89 | } 90 | 91 | @Override 92 | public void deleteAllSessions(String name) { 93 | sessionStore.deleteAllSessions(name); 94 | } 95 | 96 | @Override 97 | public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { 98 | return signedPreKeyStore.loadSignedPreKey(signedPreKeyId); 99 | } 100 | 101 | @Override 102 | public List loadSignedPreKeys() { 103 | return signedPreKeyStore.loadSignedPreKeys(); 104 | } 105 | 106 | @Override 107 | public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { 108 | signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); 109 | } 110 | 111 | @Override 112 | public boolean containsSignedPreKey(int signedPreKeyId) { 113 | return signedPreKeyStore.containsSignedPreKey(signedPreKeyId); 114 | } 115 | 116 | @Override 117 | public void removeSignedPreKey(int signedPreKeyId) { 118 | signedPreKeyStore.removeSignedPreKey(signedPreKeyId); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/ScannableFingerprint.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | 6 | import org.whispersystems.libsignal.IdentityKey; 7 | import org.whispersystems.libsignal.fingerprint.FingerprintProtos.CombinedFingerprint; 8 | import org.whispersystems.libsignal.fingerprint.FingerprintProtos.FingerprintData; 9 | 10 | import java.security.MessageDigest; 11 | 12 | public class ScannableFingerprint { 13 | 14 | private final CombinedFingerprint combinedFingerprint; 15 | 16 | public ScannableFingerprint(int version, 17 | String localStableIdentifier, IdentityKey localIdentityKey, 18 | String remoteStableIdentifier, IdentityKey remoteIdentityKey) 19 | { 20 | this.combinedFingerprint = CombinedFingerprint.newBuilder() 21 | .setVersion(version) 22 | .setLocalFingerprint(FingerprintData.newBuilder() 23 | .setIdentifier(ByteString.copyFrom(localStableIdentifier.getBytes())) 24 | .setPublicKey(ByteString.copyFrom(localIdentityKey.serialize()))) 25 | .setRemoteFingerprint(FingerprintData.newBuilder() 26 | .setIdentifier(ByteString.copyFrom(remoteStableIdentifier.getBytes())) 27 | .setPublicKey(ByteString.copyFrom(remoteIdentityKey.serialize()))) 28 | .build(); 29 | } 30 | 31 | /** 32 | * @return A byte string to be displayed in a QR code. 33 | */ 34 | public byte[] getSerialized() { 35 | return combinedFingerprint.toByteArray(); 36 | } 37 | 38 | /** 39 | * Compare a scanned QR code with what we expect. 40 | * 41 | * @param scannedFingerprintData The scanned data 42 | * @return True if matching, otehrwise false. 43 | * @throws FingerprintVersionMismatchException if the scanned fingerprint is the wrong version. 44 | * @throws FingerprintIdentifierMismatchException if the scanned fingerprint is for the wrong stable identifier. 45 | */ 46 | public boolean compareTo(byte[] scannedFingerprintData) 47 | throws FingerprintVersionMismatchException, 48 | FingerprintIdentifierMismatchException, 49 | FingerprintParsingException 50 | { 51 | try { 52 | CombinedFingerprint scannedFingerprint = CombinedFingerprint.parseFrom(scannedFingerprintData); 53 | 54 | if (!scannedFingerprint.hasRemoteFingerprint() || !scannedFingerprint.hasLocalFingerprint() || 55 | !scannedFingerprint.hasVersion() || scannedFingerprint.getVersion() != combinedFingerprint.getVersion()) 56 | { 57 | throw new FingerprintVersionMismatchException(); 58 | } 59 | 60 | if (!combinedFingerprint.getLocalFingerprint().getIdentifier().equals(scannedFingerprint.getRemoteFingerprint().getIdentifier()) || 61 | !combinedFingerprint.getRemoteFingerprint().getIdentifier().equals(scannedFingerprint.getLocalFingerprint().getIdentifier())) 62 | { 63 | throw new FingerprintIdentifierMismatchException(new String(combinedFingerprint.getLocalFingerprint().getIdentifier().toByteArray()), 64 | new String(combinedFingerprint.getRemoteFingerprint().getIdentifier().toByteArray()), 65 | new String(scannedFingerprint.getLocalFingerprint().getIdentifier().toByteArray()), 66 | new String(scannedFingerprint.getRemoteFingerprint().getIdentifier().toByteArray())); 67 | } 68 | 69 | return MessageDigest.isEqual(combinedFingerprint.getLocalFingerprint().toByteArray(), scannedFingerprint.getRemoteFingerprint().toByteArray()) && 70 | MessageDigest.isEqual(combinedFingerprint.getRemoteFingerprint().toByteArray(), scannedFingerprint.getLocalFingerprint().toByteArray()); 71 | } catch (InvalidProtocolBufferException e) { 72 | throw new FingerprintParsingException(e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/SenderKeyMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.protocol; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | 6 | import org.whispersystems.libsignal.InvalidKeyException; 7 | import org.whispersystems.libsignal.InvalidMessageException; 8 | import org.whispersystems.libsignal.LegacyMessageException; 9 | import org.whispersystems.libsignal.ecc.Curve; 10 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 11 | import org.whispersystems.libsignal.ecc.ECPublicKey; 12 | import org.whispersystems.libsignal.util.ByteUtil; 13 | 14 | import java.text.ParseException; 15 | 16 | public class SenderKeyMessage implements CiphertextMessage { 17 | 18 | private static final int SIGNATURE_LENGTH = 64; 19 | 20 | private final int messageVersion; 21 | private final int keyId; 22 | private final int iteration; 23 | private final byte[] ciphertext; 24 | private final byte[] serialized; 25 | 26 | public SenderKeyMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException { 27 | try { 28 | byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - SIGNATURE_LENGTH, SIGNATURE_LENGTH); 29 | byte version = messageParts[0][0]; 30 | byte[] message = messageParts[1]; 31 | byte[] signature = messageParts[2]; 32 | 33 | if (ByteUtil.highBitsToInt(version) < 3) { 34 | throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); 35 | } 36 | 37 | if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { 38 | throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); 39 | } 40 | 41 | SignalProtos.SenderKeyMessage senderKeyMessage = SignalProtos.SenderKeyMessage.parseFrom(message); 42 | 43 | if (!senderKeyMessage.hasId() || 44 | !senderKeyMessage.hasIteration() || 45 | !senderKeyMessage.hasCiphertext()) 46 | { 47 | throw new InvalidMessageException("Incomplete message."); 48 | } 49 | 50 | this.serialized = serialized; 51 | this.messageVersion = ByteUtil.highBitsToInt(version); 52 | this.keyId = senderKeyMessage.getId(); 53 | this.iteration = senderKeyMessage.getIteration(); 54 | this.ciphertext = senderKeyMessage.getCiphertext().toByteArray(); 55 | } catch (InvalidProtocolBufferException | ParseException e) { 56 | throw new InvalidMessageException(e); 57 | } 58 | } 59 | 60 | public SenderKeyMessage(int keyId, int iteration, byte[] ciphertext, ECPrivateKey signatureKey) { 61 | byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)}; 62 | byte[] message = SignalProtos.SenderKeyMessage.newBuilder() 63 | .setId(keyId) 64 | .setIteration(iteration) 65 | .setCiphertext(ByteString.copyFrom(ciphertext)) 66 | .build().toByteArray(); 67 | 68 | byte[] signature = getSignature(signatureKey, ByteUtil.combine(version, message)); 69 | 70 | this.serialized = ByteUtil.combine(version, message, signature); 71 | this.messageVersion = CURRENT_VERSION; 72 | this.keyId = keyId; 73 | this.iteration = iteration; 74 | this.ciphertext = ciphertext; 75 | } 76 | 77 | public int getKeyId() { 78 | return keyId; 79 | } 80 | 81 | public int getIteration() { 82 | return iteration; 83 | } 84 | 85 | public byte[] getCipherText() { 86 | return ciphertext; 87 | } 88 | 89 | public void verifySignature(ECPublicKey signatureKey) 90 | throws InvalidMessageException 91 | { 92 | try { 93 | byte[][] parts = ByteUtil.split(serialized, serialized.length - SIGNATURE_LENGTH, SIGNATURE_LENGTH); 94 | 95 | if (!Curve.verifySignature(signatureKey, parts[0], parts[1])) { 96 | throw new InvalidMessageException("Invalid signature!"); 97 | } 98 | 99 | } catch (InvalidKeyException e) { 100 | throw new InvalidMessageException(e); 101 | } 102 | } 103 | 104 | private byte[] getSignature(ECPrivateKey signatureKey, byte[] serialized) { 105 | try { 106 | return Curve.calculateSignature(signatureKey, serialized); 107 | } catch (InvalidKeyException e) { 108 | throw new AssertionError(e); 109 | } 110 | } 111 | 112 | @Override 113 | public byte[] serialize() { 114 | return serialized; 115 | } 116 | 117 | @Override 118 | public int getType() { 119 | return CiphertextMessage.SENDERKEY_TYPE; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/GroupSessionBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.groups; 18 | 19 | import org.whispersystems.libsignal.InvalidKeyException; 20 | import org.whispersystems.libsignal.InvalidKeyIdException; 21 | import org.whispersystems.libsignal.groups.state.SenderKeyRecord; 22 | import org.whispersystems.libsignal.groups.state.SenderKeyState; 23 | import org.whispersystems.libsignal.groups.state.SenderKeyStore; 24 | import org.whispersystems.libsignal.protocol.SenderKeyDistributionMessage; 25 | import org.whispersystems.libsignal.util.KeyHelper; 26 | 27 | /** 28 | * GroupSessionBuilder is responsible for setting up group SenderKey encrypted sessions. 29 | * 30 | * Once a session has been established, {@link org.whispersystems.libsignal.groups.GroupCipher} 31 | * can be used to encrypt/decrypt messages in that session. 32 | *

33 | * The built sessions are unidirectional: they can be used either for sending or for receiving, 34 | * but not both. 35 | * 36 | * Sessions are constructed per (groupId + senderId + deviceId) tuple. Remote logical users 37 | * are identified by their senderId, and each logical recipientId can have multiple physical 38 | * devices. 39 | * 40 | * @author Moxie Marlinspike 41 | */ 42 | 43 | public class GroupSessionBuilder { 44 | 45 | private final SenderKeyStore senderKeyStore; 46 | 47 | public GroupSessionBuilder(SenderKeyStore senderKeyStore) { 48 | this.senderKeyStore = senderKeyStore; 49 | } 50 | 51 | /** 52 | * Construct a group session for receiving messages from senderKeyName. 53 | * 54 | * @param senderKeyName The (groupId, senderId, deviceId) tuple associated with the SenderKeyDistributionMessage. 55 | * @param senderKeyDistributionMessage A received SenderKeyDistributionMessage. 56 | */ 57 | public void process(SenderKeyName senderKeyName, SenderKeyDistributionMessage senderKeyDistributionMessage) { 58 | synchronized (GroupCipher.LOCK) { 59 | SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName); 60 | senderKeyRecord.addSenderKeyState(senderKeyDistributionMessage.getId(), 61 | senderKeyDistributionMessage.getIteration(), 62 | senderKeyDistributionMessage.getChainKey(), 63 | senderKeyDistributionMessage.getSignatureKey()); 64 | senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); 65 | } 66 | } 67 | 68 | /** 69 | * Construct a group session for sending messages. 70 | * 71 | * @param senderKeyName The (groupId, senderId, deviceId) tuple. In this case, 'senderId' should be the caller. 72 | * @return A SenderKeyDistributionMessage that is individually distributed to each member of the group. 73 | */ 74 | public SenderKeyDistributionMessage create(SenderKeyName senderKeyName) { 75 | synchronized (GroupCipher.LOCK) { 76 | try { 77 | SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName); 78 | 79 | if (senderKeyRecord.isEmpty()) { 80 | senderKeyRecord.setSenderKeyState(KeyHelper.generateSenderKeyId(), 81 | 0, 82 | KeyHelper.generateSenderKey(), 83 | KeyHelper.generateSenderSigningKey()); 84 | senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); 85 | } 86 | 87 | SenderKeyState state = senderKeyRecord.getSenderKeyState(); 88 | 89 | return new SenderKeyDistributionMessage(state.getKeyId(), 90 | state.getSenderChainKey().getIteration(), 91 | state.getSenderChainKey().getSeed(), 92 | state.getSigningKeyPublic()); 93 | 94 | } catch (InvalidKeyIdException | InvalidKeyException e) { 95 | throw new AssertionError(e); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.fingerprint; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import org.whispersystems.libsignal.IdentityKey; 6 | import org.whispersystems.libsignal.ecc.Curve; 7 | import org.whispersystems.libsignal.ecc.ECKeyPair; 8 | 9 | public class NumericFingerprintGeneratorTest extends TestCase { 10 | 11 | public void testMatchingFingerprints() throws FingerprintVersionMismatchException, FingerprintIdentifierMismatchException, FingerprintParsingException { 12 | ECKeyPair aliceKeyPair = Curve.generateKeyPair(); 13 | ECKeyPair bobKeyPair = Curve.generateKeyPair(); 14 | 15 | IdentityKey aliceIdentityKey = new IdentityKey(aliceKeyPair.getPublicKey()); 16 | IdentityKey bobIdentityKey = new IdentityKey(bobKeyPair.getPublicKey()); 17 | 18 | NumericFingerprintGenerator generator = new NumericFingerprintGenerator(1024); 19 | Fingerprint aliceFingerprint = generator.createFor("+14152222222", aliceIdentityKey, 20 | "+14153333333", bobIdentityKey); 21 | 22 | Fingerprint bobFingerprint = generator.createFor("+14153333333", bobIdentityKey, 23 | "+14152222222", aliceIdentityKey); 24 | 25 | assertEquals(aliceFingerprint.getDisplayableFingerprint().getDisplayText(), 26 | bobFingerprint.getDisplayableFingerprint().getDisplayText()); 27 | 28 | assertTrue(aliceFingerprint.getScannableFingerprint().compareTo(bobFingerprint.getScannableFingerprint().getSerialized())); 29 | assertTrue(bobFingerprint.getScannableFingerprint().compareTo(aliceFingerprint.getScannableFingerprint().getSerialized())); 30 | 31 | assertEquals(aliceFingerprint.getDisplayableFingerprint().getDisplayText().length(), 60); 32 | } 33 | 34 | public void testMismatchingFingerprints() throws FingerprintVersionMismatchException, FingerprintIdentifierMismatchException, FingerprintParsingException { 35 | ECKeyPair aliceKeyPair = Curve.generateKeyPair(); 36 | ECKeyPair bobKeyPair = Curve.generateKeyPair(); 37 | ECKeyPair mitmKeyPair = Curve.generateKeyPair(); 38 | 39 | IdentityKey aliceIdentityKey = new IdentityKey(aliceKeyPair.getPublicKey()); 40 | IdentityKey bobIdentityKey = new IdentityKey(bobKeyPair.getPublicKey()); 41 | IdentityKey mitmIdentityKey = new IdentityKey(mitmKeyPair.getPublicKey()); 42 | 43 | NumericFingerprintGenerator generator = new NumericFingerprintGenerator(1024); 44 | Fingerprint aliceFingerprint = generator.createFor("+14152222222", aliceIdentityKey, 45 | "+14153333333", mitmIdentityKey); 46 | 47 | Fingerprint bobFingerprint = generator.createFor("+14153333333", bobIdentityKey, 48 | "+14152222222", aliceIdentityKey); 49 | 50 | assertNotSame(aliceFingerprint.getDisplayableFingerprint().getDisplayText(), 51 | bobFingerprint.getDisplayableFingerprint().getDisplayText()); 52 | 53 | assertFalse(aliceFingerprint.getScannableFingerprint().compareTo(bobFingerprint.getScannableFingerprint().getSerialized())); 54 | assertFalse(bobFingerprint.getScannableFingerprint().compareTo(aliceFingerprint.getScannableFingerprint().getSerialized())); 55 | } 56 | 57 | public void testMismatchingIdentifiers() throws FingerprintVersionMismatchException, FingerprintParsingException { 58 | ECKeyPair aliceKeyPair = Curve.generateKeyPair(); 59 | ECKeyPair bobKeyPair = Curve.generateKeyPair(); 60 | 61 | IdentityKey aliceIdentityKey = new IdentityKey(aliceKeyPair.getPublicKey()); 62 | IdentityKey bobIdentityKey = new IdentityKey(bobKeyPair.getPublicKey()); 63 | 64 | NumericFingerprintGenerator generator = new NumericFingerprintGenerator(1024); 65 | Fingerprint aliceFingerprint = generator.createFor("+141512222222", aliceIdentityKey, 66 | "+14153333333", bobIdentityKey); 67 | 68 | Fingerprint bobFingerprint = generator.createFor("+14153333333", bobIdentityKey, 69 | "+14152222222", aliceIdentityKey); 70 | 71 | assertNotSame(aliceFingerprint.getDisplayableFingerprint().getDisplayText(), 72 | bobFingerprint.getDisplayableFingerprint().getDisplayText()); 73 | 74 | try {; 75 | aliceFingerprint.getScannableFingerprint().compareTo(bobFingerprint.getScannableFingerprint().getSerialized()); 76 | throw new AssertionError("Should mismatch!"); 77 | } catch (FingerprintIdentifierMismatchException e) { 78 | // good 79 | } 80 | 81 | try { 82 | bobFingerprint.getScannableFingerprint().compareTo(aliceFingerprint.getScannableFingerprint().getSerialized()); 83 | throw new AssertionError("Should mismatch!"); 84 | } catch (FingerprintIdentifierMismatchException e) { 85 | // good 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/ratchet/RootKeyTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.ratchet; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import org.whispersystems.libsignal.InvalidKeyException; 6 | import org.whispersystems.libsignal.ecc.Curve; 7 | import org.whispersystems.libsignal.ecc.ECKeyPair; 8 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 9 | import org.whispersystems.libsignal.ecc.ECPublicKey; 10 | import org.whispersystems.libsignal.kdf.HKDF; 11 | import org.whispersystems.libsignal.util.Pair; 12 | 13 | import java.security.NoSuchAlgorithmException; 14 | import java.util.Arrays; 15 | 16 | public class RootKeyTest extends TestCase { 17 | 18 | public void testRootKeyDerivationV2() throws NoSuchAlgorithmException, InvalidKeyException { 19 | byte[] rootKeySeed = {(byte) 0x7b, (byte) 0xa6, (byte) 0xde, (byte) 0xbc, (byte) 0x2b, 20 | (byte) 0xc1, (byte) 0xbb, (byte) 0xf9, (byte) 0x1a, (byte) 0xbb, 21 | (byte) 0xc1, (byte) 0x36, (byte) 0x74, (byte) 0x04, (byte) 0x17, 22 | (byte) 0x6c, (byte) 0xa6, (byte) 0x23, (byte) 0x09, (byte) 0x5b, 23 | (byte) 0x7e, (byte) 0xc6, (byte) 0x6b, (byte) 0x45, (byte) 0xf6, 24 | (byte) 0x02, (byte) 0xd9, (byte) 0x35, (byte) 0x38, (byte) 0x94, 25 | (byte) 0x2d, (byte) 0xcc}; 26 | 27 | byte[] alicePublic = {(byte) 0x05, (byte) 0xee, (byte) 0x4f, (byte) 0xa6, (byte) 0xcd, 28 | (byte) 0xc0, (byte) 0x30, (byte) 0xdf, (byte) 0x49, (byte) 0xec, 29 | (byte) 0xd0, (byte) 0xba, (byte) 0x6c, (byte) 0xfc, (byte) 0xff, 30 | (byte) 0xb2, (byte) 0x33, (byte) 0xd3, (byte) 0x65, (byte) 0xa2, 31 | (byte) 0x7f, (byte) 0xad, (byte) 0xbe, (byte) 0xff, (byte) 0x77, 32 | (byte) 0xe9, (byte) 0x63, (byte) 0xfc, (byte) 0xb1, (byte) 0x62, 33 | (byte) 0x22, (byte) 0xe1, (byte) 0x3a}; 34 | 35 | byte[] alicePrivate = {(byte) 0x21, (byte) 0x68, (byte) 0x22, (byte) 0xec, (byte) 0x67, 36 | (byte) 0xeb, (byte) 0x38, (byte) 0x04, (byte) 0x9e, (byte) 0xba, 37 | (byte) 0xe7, (byte) 0xb9, (byte) 0x39, (byte) 0xba, (byte) 0xea, 38 | (byte) 0xeb, (byte) 0xb1, (byte) 0x51, (byte) 0xbb, (byte) 0xb3, 39 | (byte) 0x2d, (byte) 0xb8, (byte) 0x0f, (byte) 0xd3, (byte) 0x89, 40 | (byte) 0x24, (byte) 0x5a, (byte) 0xc3, (byte) 0x7a, (byte) 0x94, 41 | (byte) 0x8e, (byte) 0x50}; 42 | 43 | byte[] bobPublic = {(byte) 0x05, (byte) 0xab, (byte) 0xb8, (byte) 0xeb, (byte) 0x29, 44 | (byte) 0xcc, (byte) 0x80, (byte) 0xb4, (byte) 0x71, (byte) 0x09, 45 | (byte) 0xa2, (byte) 0x26, (byte) 0x5a, (byte) 0xbe, (byte) 0x97, 46 | (byte) 0x98, (byte) 0x48, (byte) 0x54, (byte) 0x06, (byte) 0xe3, 47 | (byte) 0x2d, (byte) 0xa2, (byte) 0x68, (byte) 0x93, (byte) 0x4a, 48 | (byte) 0x95, (byte) 0x55, (byte) 0xe8, (byte) 0x47, (byte) 0x57, 49 | (byte) 0x70, (byte) 0x8a, (byte) 0x30}; 50 | 51 | byte[] nextRoot = {(byte) 0xb1, (byte) 0x14, (byte) 0xf5, (byte) 0xde, (byte) 0x28, 52 | (byte) 0x01, (byte) 0x19, (byte) 0x85, (byte) 0xe6, (byte) 0xeb, 53 | (byte) 0xa2, (byte) 0x5d, (byte) 0x50, (byte) 0xe7, (byte) 0xec, 54 | (byte) 0x41, (byte) 0xa9, (byte) 0xb0, (byte) 0x2f, (byte) 0x56, 55 | (byte) 0x93, (byte) 0xc5, (byte) 0xc7, (byte) 0x88, (byte) 0xa6, 56 | (byte) 0x3a, (byte) 0x06, (byte) 0xd2, (byte) 0x12, (byte) 0xa2, 57 | (byte) 0xf7, (byte) 0x31}; 58 | 59 | byte[] nextChain = {(byte) 0x9d, (byte) 0x7d, (byte) 0x24, (byte) 0x69, (byte) 0xbc, 60 | (byte) 0x9a, (byte) 0xe5, (byte) 0x3e, (byte) 0xe9, (byte) 0x80, 61 | (byte) 0x5a, (byte) 0xa3, (byte) 0x26, (byte) 0x4d, (byte) 0x24, 62 | (byte) 0x99, (byte) 0xa3, (byte) 0xac, (byte) 0xe8, (byte) 0x0f, 63 | (byte) 0x4c, (byte) 0xca, (byte) 0xe2, (byte) 0xda, (byte) 0x13, 64 | (byte) 0x43, (byte) 0x0c, (byte) 0x5c, (byte) 0x55, (byte) 0xb5, 65 | (byte) 0xca, (byte) 0x5f}; 66 | 67 | ECPublicKey alicePublicKey = Curve.decodePoint(alicePublic, 0); 68 | ECPrivateKey alicePrivateKey = Curve.decodePrivatePoint(alicePrivate); 69 | ECKeyPair aliceKeyPair = new ECKeyPair(alicePublicKey, alicePrivateKey); 70 | 71 | ECPublicKey bobPublicKey = Curve.decodePoint(bobPublic, 0); 72 | RootKey rootKey = new RootKey(HKDF.createFor(2), rootKeySeed); 73 | 74 | Pair rootKeyChainKeyPair = rootKey.createChain(bobPublicKey, aliceKeyPair); 75 | RootKey nextRootKey = rootKeyChainKeyPair.first(); 76 | ChainKey nextChainKey = rootKeyChainKeyPair.second(); 77 | 78 | assertTrue(Arrays.equals(rootKey.getKeyBytes(), rootKeySeed)); 79 | assertTrue(Arrays.equals(nextRootKey.getKeyBytes(), nextRoot)); 80 | assertTrue(Arrays.equals(nextChainKey.getKey(), nextChain)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/KeyHelper.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.util; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.IdentityKeyPair; 5 | import org.whispersystems.libsignal.InvalidKeyException; 6 | import org.whispersystems.libsignal.ecc.Curve; 7 | import org.whispersystems.libsignal.ecc.ECKeyPair; 8 | import org.whispersystems.libsignal.state.PreKeyRecord; 9 | import org.whispersystems.libsignal.state.SignedPreKeyRecord; 10 | 11 | import java.security.NoSuchAlgorithmException; 12 | import java.security.SecureRandom; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | /** 17 | * Helper class for generating keys of different types. 18 | * 19 | * @author Moxie Marlinspike 20 | */ 21 | public class KeyHelper { 22 | 23 | private KeyHelper() {} 24 | 25 | /** 26 | * Generate an identity key pair. Clients should only do this once, 27 | * at install time. 28 | * 29 | * @return the generated IdentityKeyPair. 30 | */ 31 | public static IdentityKeyPair generateIdentityKeyPair() { 32 | ECKeyPair keyPair = Curve.generateKeyPair(); 33 | IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey()); 34 | return new IdentityKeyPair(publicKey, keyPair.getPrivateKey()); 35 | } 36 | 37 | /** 38 | * Generate a registration ID. Clients should only do this once, 39 | * at install time. 40 | * 41 | * @param extendedRange By default (false), the generated registration 42 | * ID is sized to require the minimal possible protobuf 43 | * encoding overhead. Specify true if the caller needs 44 | * the full range of MAX_INT at the cost of slightly 45 | * higher encoding overhead. 46 | * @return the generated registration ID. 47 | */ 48 | public static int generateRegistrationId(boolean extendedRange) { 49 | try { 50 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 51 | if (extendedRange) return secureRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 52 | else return secureRandom.nextInt(16380) + 1; 53 | } catch (NoSuchAlgorithmException e) { 54 | throw new AssertionError(e); 55 | } 56 | } 57 | 58 | public static int getRandomSequence(int max) { 59 | try { 60 | return SecureRandom.getInstance("SHA1PRNG").nextInt(max); 61 | } catch (NoSuchAlgorithmException e) { 62 | throw new AssertionError(e); 63 | } 64 | } 65 | 66 | /** 67 | * Generate a list of PreKeys. Clients should do this at install time, and 68 | * subsequently any time the list of PreKeys stored on the server runs low. 69 | *

70 | * PreKey IDs are shorts, so they will eventually be repeated. Clients should 71 | * store PreKeys in a circular buffer, so that they are repeated as infrequently 72 | * as possible. 73 | * 74 | * @param start The starting PreKey ID, inclusive. 75 | * @param count The number of PreKeys to generate. 76 | * @return the list of generated PreKeyRecords. 77 | */ 78 | public static List generatePreKeys(int start, int count) { 79 | List results = new LinkedList<>(); 80 | 81 | start--; 82 | 83 | for (int i=0;i 56 | org.whispersystems 57 | signal-protocol-java 58 | (latest version number) 59 | 60 | ``` 61 | 62 | ## Install time 63 | 64 | At install time, a libsignal client needs to generate its identity keys, registration id, and 65 | prekeys. 66 | 67 | IdentityKeyPair identityKeyPair = KeyHelper.generateIdentityKeyPair(); 68 | int registrationId = KeyHelper.generateRegistrationId(); 69 | List preKeys = KeyHelper.generatePreKeys(startId, 100); 70 | SignedPreKeyRecord signedPreKey = KeyHelper.generateSignedPreKey(identityKeyPair, 5); 71 | 72 | // Store identityKeyPair somewhere durable and safe. 73 | // Store registrationId somewhere durable and safe. 74 | 75 | // Store preKeys in PreKeyStore. 76 | // Store signed prekey in SignedPreKeyStore. 77 | 78 | ## Building a session 79 | 80 | A libsignal client needs to implement four interfaces: IdentityKeyStore, PreKeyStore, 81 | SignedPreKeyStore, and SessionStore. These will manage loading and storing of identity, 82 | prekeys, signed prekeys, and session state. 83 | 84 | Once those are implemented, building a session is fairly straightforward: 85 | 86 | SessionStore sessionStore = new MySessionStore(); 87 | PreKeyStore preKeyStore = new MyPreKeyStore(); 88 | SignedPreKeyStore signedPreKeyStore = new MySignedPreKeyStore(); 89 | IdentityKeyStore identityStore = new MyIdentityKeyStore(); 90 | 91 | // Instantiate a SessionBuilder for a remote recipientId + deviceId tuple. 92 | SessionBuilder sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore, 93 | identityStore, recipientId, deviceId); 94 | 95 | // Build a session with a PreKey retrieved from the server. 96 | sessionBuilder.process(retrievedPreKey); 97 | 98 | SessionCipher sessionCipher = new SessionCipher(sessionStore, recipientId, deviceId); 99 | CiphertextMessage message = sessionCipher.encrypt("Hello world!".getBytes("UTF-8")); 100 | 101 | deliver(message.serialize()); 102 | 103 | # Legal things 104 | ## Cryptography Notice 105 | 106 | This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. 107 | BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. 108 | See for more information. 109 | 110 | The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms. 111 | The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code. 112 | 113 | ## License 114 | 115 | Copyright 2013-2016 Open Whisper Systems 116 | 117 | Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html 118 | 119 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/KeyExchangeMessage.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.protocol; 2 | 3 | 4 | import com.google.protobuf.ByteString; 5 | 6 | import org.whispersystems.libsignal.IdentityKey; 7 | import org.whispersystems.libsignal.InvalidKeyException; 8 | import org.whispersystems.libsignal.InvalidMessageException; 9 | import org.whispersystems.libsignal.InvalidVersionException; 10 | import org.whispersystems.libsignal.LegacyMessageException; 11 | import org.whispersystems.libsignal.ecc.Curve; 12 | import org.whispersystems.libsignal.ecc.ECPublicKey; 13 | import org.whispersystems.libsignal.util.ByteUtil; 14 | 15 | import java.io.IOException; 16 | 17 | import static org.whispersystems.libsignal.protocol.SignalProtos.KeyExchangeMessage.Builder; 18 | 19 | public class KeyExchangeMessage { 20 | 21 | public static final int INITIATE_FLAG = 0x01; 22 | public static final int RESPONSE_FLAG = 0X02; 23 | public static final int SIMULTAENOUS_INITIATE_FLAG = 0x04; 24 | 25 | private final int version; 26 | private final int supportedVersion; 27 | private final int sequence; 28 | private final int flags; 29 | 30 | private final ECPublicKey baseKey; 31 | private final byte[] baseKeySignature; 32 | private final ECPublicKey ratchetKey; 33 | private final IdentityKey identityKey; 34 | private final byte[] serialized; 35 | 36 | public KeyExchangeMessage(int messageVersion, int sequence, int flags, 37 | ECPublicKey baseKey, byte[] baseKeySignature, 38 | ECPublicKey ratchetKey, 39 | IdentityKey identityKey) 40 | { 41 | this.supportedVersion = CiphertextMessage.CURRENT_VERSION; 42 | this.version = messageVersion; 43 | this.sequence = sequence; 44 | this.flags = flags; 45 | this.baseKey = baseKey; 46 | this.baseKeySignature = baseKeySignature; 47 | this.ratchetKey = ratchetKey; 48 | this.identityKey = identityKey; 49 | 50 | byte[] version = {ByteUtil.intsToByteHighAndLow(this.version, this.supportedVersion)}; 51 | Builder builder = SignalProtos.KeyExchangeMessage 52 | .newBuilder() 53 | .setId((sequence << 5) | flags) 54 | .setBaseKey(ByteString.copyFrom(baseKey.serialize())) 55 | .setRatchetKey(ByteString.copyFrom(ratchetKey.serialize())) 56 | .setIdentityKey(ByteString.copyFrom(identityKey.serialize())); 57 | 58 | if (messageVersion >= 3) { 59 | builder.setBaseKeySignature(ByteString.copyFrom(baseKeySignature)); 60 | } 61 | 62 | this.serialized = ByteUtil.combine(version, builder.build().toByteArray()); 63 | } 64 | 65 | public KeyExchangeMessage(byte[] serialized) 66 | throws InvalidMessageException, InvalidVersionException, LegacyMessageException 67 | { 68 | try { 69 | byte[][] parts = ByteUtil.split(serialized, 1, serialized.length - 1); 70 | this.version = ByteUtil.highBitsToInt(parts[0][0]); 71 | this.supportedVersion = ByteUtil.lowBitsToInt(parts[0][0]); 72 | 73 | if (this.version < CiphertextMessage.CURRENT_VERSION) { 74 | throw new LegacyMessageException("Unsupported legacy version: " + this.version); 75 | } 76 | 77 | if (this.version > CiphertextMessage.CURRENT_VERSION) { 78 | throw new InvalidVersionException("Unknown version: " + this.version); 79 | } 80 | 81 | SignalProtos.KeyExchangeMessage message = SignalProtos.KeyExchangeMessage.parseFrom(parts[1]); 82 | 83 | if (!message.hasId() || !message.hasBaseKey() || 84 | !message.hasRatchetKey() || !message.hasIdentityKey() || 85 | !message.hasBaseKeySignature()) 86 | { 87 | throw new InvalidMessageException("Some required fields missing!"); 88 | } 89 | 90 | this.sequence = message.getId() >> 5; 91 | this.flags = message.getId() & 0x1f; 92 | this.serialized = serialized; 93 | this.baseKey = Curve.decodePoint(message.getBaseKey().toByteArray(), 0); 94 | this.baseKeySignature = message.getBaseKeySignature().toByteArray(); 95 | this.ratchetKey = Curve.decodePoint(message.getRatchetKey().toByteArray(), 0); 96 | this.identityKey = new IdentityKey(message.getIdentityKey().toByteArray(), 0); 97 | } catch (InvalidKeyException | IOException e) { 98 | throw new InvalidMessageException(e); 99 | } 100 | } 101 | 102 | public int getVersion() { 103 | return version; 104 | } 105 | 106 | public ECPublicKey getBaseKey() { 107 | return baseKey; 108 | } 109 | 110 | public byte[] getBaseKeySignature() { 111 | return baseKeySignature; 112 | } 113 | 114 | public ECPublicKey getRatchetKey() { 115 | return ratchetKey; 116 | } 117 | 118 | public IdentityKey getIdentityKey() { 119 | return identityKey; 120 | } 121 | 122 | public boolean hasIdentityKey() { 123 | return true; 124 | } 125 | 126 | public int getMaxVersion() { 127 | return supportedVersion; 128 | } 129 | 130 | public boolean isResponse() { 131 | return ((flags & RESPONSE_FLAG) != 0); 132 | } 133 | 134 | public boolean isInitiate() { 135 | return (flags & INITIATE_FLAG) != 0; 136 | } 137 | 138 | public boolean isResponseForSimultaneousInitiate() { 139 | return (flags & SIMULTAENOUS_INITIATE_FLAG) != 0; 140 | } 141 | 142 | public int getFlags() { 143 | return flags; 144 | } 145 | 146 | public int getSequence() { 147 | return sequence; 148 | } 149 | 150 | public byte[] serialize() { 151 | return serialized; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/PreKeySignalMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.protocol; 18 | 19 | import com.google.protobuf.ByteString; 20 | import com.google.protobuf.InvalidProtocolBufferException; 21 | 22 | import org.whispersystems.libsignal.IdentityKey; 23 | import org.whispersystems.libsignal.InvalidKeyException; 24 | import org.whispersystems.libsignal.InvalidMessageException; 25 | import org.whispersystems.libsignal.InvalidVersionException; 26 | import org.whispersystems.libsignal.LegacyMessageException; 27 | import org.whispersystems.libsignal.ecc.Curve; 28 | import org.whispersystems.libsignal.ecc.ECPublicKey; 29 | import org.whispersystems.libsignal.util.ByteUtil; 30 | import org.whispersystems.libsignal.util.guava.Optional; 31 | 32 | 33 | public class PreKeySignalMessage implements CiphertextMessage { 34 | 35 | private final int version; 36 | private final int registrationId; 37 | private final Optional preKeyId; 38 | private final int signedPreKeyId; 39 | private final ECPublicKey baseKey; 40 | private final IdentityKey identityKey; 41 | private final SignalMessage message; 42 | private final byte[] serialized; 43 | 44 | public PreKeySignalMessage(byte[] serialized) 45 | throws InvalidMessageException, InvalidVersionException 46 | { 47 | try { 48 | this.version = ByteUtil.highBitsToInt(serialized[0]); 49 | 50 | if (this.version > CiphertextMessage.CURRENT_VERSION) { 51 | throw new InvalidVersionException("Unknown version: " + this.version); 52 | } 53 | 54 | if (this.version < CiphertextMessage.CURRENT_VERSION) { 55 | throw new LegacyMessageException("Legacy version: " + this.version); 56 | } 57 | 58 | SignalProtos.PreKeySignalMessage preKeyWhisperMessage 59 | = SignalProtos.PreKeySignalMessage.parseFrom(ByteString.copyFrom(serialized, 1, 60 | serialized.length-1)); 61 | 62 | if (!preKeyWhisperMessage.hasSignedPreKeyId() || 63 | !preKeyWhisperMessage.hasBaseKey() || 64 | !preKeyWhisperMessage.hasIdentityKey() || 65 | !preKeyWhisperMessage.hasMessage()) 66 | { 67 | throw new InvalidMessageException("Incomplete message."); 68 | } 69 | 70 | this.serialized = serialized; 71 | this.registrationId = preKeyWhisperMessage.getRegistrationId(); 72 | this.preKeyId = preKeyWhisperMessage.hasPreKeyId() ? Optional.of(preKeyWhisperMessage.getPreKeyId()) : Optional.absent(); 73 | this.signedPreKeyId = preKeyWhisperMessage.hasSignedPreKeyId() ? preKeyWhisperMessage.getSignedPreKeyId() : -1; 74 | this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0); 75 | this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0)); 76 | this.message = new SignalMessage(preKeyWhisperMessage.getMessage().toByteArray()); 77 | } catch (InvalidProtocolBufferException | InvalidKeyException | LegacyMessageException e) { 78 | throw new InvalidMessageException(e); 79 | } 80 | } 81 | 82 | public PreKeySignalMessage(int messageVersion, int registrationId, Optional preKeyId, 83 | int signedPreKeyId, ECPublicKey baseKey, IdentityKey identityKey, 84 | SignalMessage message) 85 | { 86 | this.version = messageVersion; 87 | this.registrationId = registrationId; 88 | this.preKeyId = preKeyId; 89 | this.signedPreKeyId = signedPreKeyId; 90 | this.baseKey = baseKey; 91 | this.identityKey = identityKey; 92 | this.message = message; 93 | 94 | SignalProtos.PreKeySignalMessage.Builder builder = 95 | SignalProtos.PreKeySignalMessage.newBuilder() 96 | .setSignedPreKeyId(signedPreKeyId) 97 | .setBaseKey(ByteString.copyFrom(baseKey.serialize())) 98 | .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) 99 | .setMessage(ByteString.copyFrom(message.serialize())) 100 | .setRegistrationId(registrationId); 101 | 102 | if (preKeyId.isPresent()) { 103 | builder.setPreKeyId(preKeyId.get()); 104 | } 105 | 106 | byte[] versionBytes = {ByteUtil.intsToByteHighAndLow(this.version, CURRENT_VERSION)}; 107 | byte[] messageBytes = builder.build().toByteArray(); 108 | 109 | this.serialized = ByteUtil.combine(versionBytes, messageBytes); 110 | } 111 | 112 | public int getMessageVersion() { 113 | return version; 114 | } 115 | 116 | public IdentityKey getIdentityKey() { 117 | return identityKey; 118 | } 119 | 120 | public int getRegistrationId() { 121 | return registrationId; 122 | } 123 | 124 | public Optional getPreKeyId() { 125 | return preKeyId; 126 | } 127 | 128 | public int getSignedPreKeyId() { 129 | return signedPreKeyId; 130 | } 131 | 132 | public ECPublicKey getBaseKey() { 133 | return baseKey; 134 | } 135 | 136 | public SignalMessage getWhisperMessage() { 137 | return message; 138 | } 139 | 140 | @Override 141 | public byte[] serialize() { 142 | return serialized; 143 | } 144 | 145 | @Override 146 | public int getType() { 147 | return CiphertextMessage.PREKEY_TYPE; 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/ratchet/ChainKeyTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.ratchet; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import org.whispersystems.libsignal.kdf.HKDF; 6 | 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.Arrays; 9 | 10 | public class ChainKeyTest extends TestCase { 11 | 12 | public void testChainKeyDerivationV2() throws NoSuchAlgorithmException { 13 | 14 | byte[] seed = {(byte) 0x8a, (byte) 0xb7, (byte) 0x2d, (byte) 0x6f, (byte) 0x4c, 15 | (byte) 0xc5, (byte) 0xac, (byte) 0x0d, (byte) 0x38, (byte) 0x7e, 16 | (byte) 0xaf, (byte) 0x46, (byte) 0x33, (byte) 0x78, (byte) 0xdd, 17 | (byte) 0xb2, (byte) 0x8e, (byte) 0xdd, (byte) 0x07, (byte) 0x38, 18 | (byte) 0x5b, (byte) 0x1c, (byte) 0xb0, (byte) 0x12, (byte) 0x50, 19 | (byte) 0xc7, (byte) 0x15, (byte) 0x98, (byte) 0x2e, (byte) 0x7a, 20 | (byte) 0xd4, (byte) 0x8f}; 21 | 22 | byte[] messageKey = {(byte) 0x02, (byte) 0xa9, (byte) 0xaa, (byte) 0x6c, (byte) 0x7d, 23 | (byte) 0xbd, (byte) 0x64, (byte) 0xf9, (byte) 0xd3, (byte) 0xaa, 24 | (byte) 0x92, (byte) 0xf9, (byte) 0x2a, (byte) 0x27, (byte) 0x7b, 25 | (byte) 0xf5, (byte) 0x46, (byte) 0x09, (byte) 0xda, (byte) 0xdf, 26 | (byte) 0x0b, (byte) 0x00, (byte) 0x82, (byte) 0x8a, (byte) 0xcf, 27 | (byte) 0xc6, (byte) 0x1e, (byte) 0x3c, (byte) 0x72, (byte) 0x4b, 28 | (byte) 0x84, (byte) 0xa7}; 29 | 30 | byte[] macKey = {(byte) 0xbf, (byte) 0xbe, (byte) 0x5e, (byte) 0xfb, (byte) 0x60, 31 | (byte) 0x30, (byte) 0x30, (byte) 0x52, (byte) 0x67, (byte) 0x42, 32 | (byte) 0xe3, (byte) 0xee, (byte) 0x89, (byte) 0xc7, (byte) 0x02, 33 | (byte) 0x4e, (byte) 0x88, (byte) 0x4e, (byte) 0x44, (byte) 0x0f, 34 | (byte) 0x1f, (byte) 0xf3, (byte) 0x76, (byte) 0xbb, (byte) 0x23, 35 | (byte) 0x17, (byte) 0xb2, (byte) 0xd6, (byte) 0x4d, (byte) 0xeb, 36 | (byte) 0x7c, (byte) 0x83}; 37 | 38 | byte[] nextChainKey = {(byte) 0x28, (byte) 0xe8, (byte) 0xf8, (byte) 0xfe, (byte) 0xe5, 39 | (byte) 0x4b, (byte) 0x80, (byte) 0x1e, (byte) 0xef, (byte) 0x7c, 40 | (byte) 0x5c, (byte) 0xfb, (byte) 0x2f, (byte) 0x17, (byte) 0xf3, 41 | (byte) 0x2c, (byte) 0x7b, (byte) 0x33, (byte) 0x44, (byte) 0x85, 42 | (byte) 0xbb, (byte) 0xb7, (byte) 0x0f, (byte) 0xac, (byte) 0x6e, 43 | (byte) 0xc1, (byte) 0x03, (byte) 0x42, (byte) 0xa2, (byte) 0x46, 44 | (byte) 0xd1, (byte) 0x5d}; 45 | 46 | ChainKey chainKey = new ChainKey(HKDF.createFor(2), seed, 0); 47 | 48 | assertTrue(Arrays.equals(chainKey.getKey(), seed)); 49 | assertTrue(Arrays.equals(chainKey.getMessageKeys().getCipherKey().getEncoded(), messageKey)); 50 | assertTrue(Arrays.equals(chainKey.getMessageKeys().getMacKey().getEncoded(), macKey)); 51 | assertTrue(Arrays.equals(chainKey.getNextChainKey().getKey(), nextChainKey)); 52 | assertTrue(chainKey.getIndex() == 0); 53 | assertTrue(chainKey.getMessageKeys().getCounter() == 0); 54 | assertTrue(chainKey.getNextChainKey().getIndex() == 1); 55 | assertTrue(chainKey.getNextChainKey().getMessageKeys().getCounter() == 1); 56 | } 57 | 58 | public void testChainKeyDerivationV3() throws NoSuchAlgorithmException { 59 | 60 | byte[] seed = { 61 | (byte) 0x8a, (byte) 0xb7, (byte) 0x2d, (byte) 0x6f, (byte) 0x4c, 62 | (byte) 0xc5, (byte) 0xac, (byte) 0x0d, (byte) 0x38, (byte) 0x7e, 63 | (byte) 0xaf, (byte) 0x46, (byte) 0x33, (byte) 0x78, (byte) 0xdd, 64 | (byte) 0xb2, (byte) 0x8e, (byte) 0xdd, (byte) 0x07, (byte) 0x38, 65 | (byte) 0x5b, (byte) 0x1c, (byte) 0xb0, (byte) 0x12, (byte) 0x50, 66 | (byte) 0xc7, (byte) 0x15, (byte) 0x98, (byte) 0x2e, (byte) 0x7a, 67 | (byte) 0xd4, (byte) 0x8f}; 68 | 69 | byte[] messageKey = { 70 | /* (byte) 0x02*/ 71 | (byte) 0xbf, (byte) 0x51, (byte) 0xe9, (byte) 0xd7, 72 | (byte) 0x5e, (byte) 0x0e, (byte) 0x31, (byte) 0x03, (byte) 0x10, 73 | (byte) 0x51, (byte) 0xf8, (byte) 0x2a, (byte) 0x24, (byte) 0x91, 74 | (byte) 0xff, (byte) 0xc0, (byte) 0x84, (byte) 0xfa, (byte) 0x29, 75 | (byte) 0x8b, (byte) 0x77, (byte) 0x93, (byte) 0xbd, (byte) 0x9d, 76 | (byte) 0xb6, (byte) 0x20, (byte) 0x05, (byte) 0x6f, (byte) 0xeb, 77 | (byte) 0xf4, (byte) 0x52, (byte) 0x17}; 78 | 79 | byte[] macKey = { 80 | (byte)0xc6, (byte)0xc7, (byte)0x7d, (byte)0x6a, (byte)0x73, 81 | (byte)0xa3, (byte)0x54, (byte)0x33, (byte)0x7a, (byte)0x56, 82 | (byte)0x43, (byte)0x5e, (byte)0x34, (byte)0x60, (byte)0x7d, 83 | (byte)0xfe, (byte)0x48, (byte)0xe3, (byte)0xac, (byte)0xe1, 84 | (byte)0x4e, (byte)0x77, (byte)0x31, (byte)0x4d, (byte)0xc6, 85 | (byte)0xab, (byte)0xc1, (byte)0x72, (byte)0xe7, (byte)0xa7, 86 | (byte)0x03, (byte)0x0b}; 87 | 88 | byte[] nextChainKey = { 89 | (byte) 0x28, (byte) 0xe8, (byte) 0xf8, (byte) 0xfe, (byte) 0xe5, 90 | (byte) 0x4b, (byte) 0x80, (byte) 0x1e, (byte) 0xef, (byte) 0x7c, 91 | (byte) 0x5c, (byte) 0xfb, (byte) 0x2f, (byte) 0x17, (byte) 0xf3, 92 | (byte) 0x2c, (byte) 0x7b, (byte) 0x33, (byte) 0x44, (byte) 0x85, 93 | (byte) 0xbb, (byte) 0xb7, (byte) 0x0f, (byte) 0xac, (byte) 0x6e, 94 | (byte) 0xc1, (byte) 0x03, (byte) 0x42, (byte) 0xa2, (byte) 0x46, 95 | (byte) 0xd1, (byte) 0x5d}; 96 | 97 | ChainKey chainKey = new ChainKey(HKDF.createFor(3), seed, 0); 98 | 99 | assertTrue(Arrays.equals(chainKey.getKey(), seed)); 100 | assertTrue(Arrays.equals(chainKey.getMessageKeys().getCipherKey().getEncoded(), messageKey)); 101 | assertTrue(Arrays.equals(chainKey.getMessageKeys().getMacKey().getEncoded(), macKey)); 102 | assertTrue(Arrays.equals(chainKey.getNextChainKey().getKey(), nextChainKey)); 103 | assertTrue(chainKey.getIndex() == 0); 104 | assertTrue(chainKey.getMessageKeys().getCounter() == 0); 105 | assertTrue(chainKey.getNextChainKey().getIndex() == 1); 106 | assertTrue(chainKey.getNextChainKey().getMessageKeys().getCounter() == 1); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/SignalMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.protocol; 18 | 19 | import com.google.protobuf.ByteString; 20 | import com.google.protobuf.InvalidProtocolBufferException; 21 | 22 | import org.whispersystems.libsignal.IdentityKey; 23 | import org.whispersystems.libsignal.InvalidKeyException; 24 | import org.whispersystems.libsignal.InvalidMessageException; 25 | import org.whispersystems.libsignal.LegacyMessageException; 26 | import org.whispersystems.libsignal.ecc.Curve; 27 | import org.whispersystems.libsignal.ecc.ECPublicKey; 28 | import org.whispersystems.libsignal.util.ByteUtil; 29 | 30 | import java.security.MessageDigest; 31 | import java.security.NoSuchAlgorithmException; 32 | import java.text.ParseException; 33 | 34 | import javax.crypto.Mac; 35 | import javax.crypto.spec.SecretKeySpec; 36 | 37 | public class SignalMessage implements CiphertextMessage { 38 | 39 | private static final int MAC_LENGTH = 8; 40 | 41 | private final int messageVersion; 42 | private final ECPublicKey senderRatchetKey; 43 | private final int counter; 44 | private final int previousCounter; 45 | private final byte[] ciphertext; 46 | private final byte[] serialized; 47 | 48 | public SignalMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException { 49 | try { 50 | byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH); 51 | byte version = messageParts[0][0]; 52 | byte[] message = messageParts[1]; 53 | byte[] mac = messageParts[2]; 54 | 55 | if (ByteUtil.highBitsToInt(version) <= CiphertextMessage.UNSUPPORTED_VERSION) { 56 | throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); 57 | } 58 | 59 | if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { 60 | throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); 61 | } 62 | 63 | SignalProtos.SignalMessage whisperMessage = SignalProtos.SignalMessage.parseFrom(message); 64 | 65 | if (!whisperMessage.hasCiphertext() || 66 | !whisperMessage.hasCounter() || 67 | !whisperMessage.hasRatchetKey()) 68 | { 69 | throw new InvalidMessageException("Incomplete message."); 70 | } 71 | 72 | this.serialized = serialized; 73 | this.senderRatchetKey = Curve.decodePoint(whisperMessage.getRatchetKey().toByteArray(), 0); 74 | this.messageVersion = ByteUtil.highBitsToInt(version); 75 | this.counter = whisperMessage.getCounter(); 76 | this.previousCounter = whisperMessage.getPreviousCounter(); 77 | this.ciphertext = whisperMessage.getCiphertext().toByteArray(); 78 | } catch (InvalidProtocolBufferException | InvalidKeyException | ParseException e) { 79 | throw new InvalidMessageException(e); 80 | } 81 | } 82 | 83 | public SignalMessage(int messageVersion, SecretKeySpec macKey, ECPublicKey senderRatchetKey, 84 | int counter, int previousCounter, byte[] ciphertext, 85 | IdentityKey senderIdentityKey, 86 | IdentityKey receiverIdentityKey) 87 | { 88 | byte[] version = {ByteUtil.intsToByteHighAndLow(messageVersion, CURRENT_VERSION)}; 89 | byte[] message = SignalProtos.SignalMessage.newBuilder() 90 | .setRatchetKey(ByteString.copyFrom(senderRatchetKey.serialize())) 91 | .setCounter(counter) 92 | .setPreviousCounter(previousCounter) 93 | .setCiphertext(ByteString.copyFrom(ciphertext)) 94 | .build().toByteArray(); 95 | 96 | byte[] mac = getMac(messageVersion, senderIdentityKey, receiverIdentityKey, macKey, 97 | ByteUtil.combine(version, message)); 98 | 99 | this.serialized = ByteUtil.combine(version, message, mac); 100 | this.senderRatchetKey = senderRatchetKey; 101 | this.counter = counter; 102 | this.previousCounter = previousCounter; 103 | this.ciphertext = ciphertext; 104 | this.messageVersion = messageVersion; 105 | } 106 | 107 | public ECPublicKey getSenderRatchetKey() { 108 | return senderRatchetKey; 109 | } 110 | 111 | public int getMessageVersion() { 112 | return messageVersion; 113 | } 114 | 115 | public int getCounter() { 116 | return counter; 117 | } 118 | 119 | public byte[] getBody() { 120 | return ciphertext; 121 | } 122 | 123 | public void verifyMac(int messageVersion, IdentityKey senderIdentityKey, 124 | IdentityKey receiverIdentityKey, SecretKeySpec macKey) 125 | throws InvalidMessageException 126 | { 127 | byte[][] parts = ByteUtil.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH); 128 | byte[] ourMac = getMac(messageVersion, senderIdentityKey, receiverIdentityKey, macKey, parts[0]); 129 | byte[] theirMac = parts[1]; 130 | 131 | if (!MessageDigest.isEqual(ourMac, theirMac)) { 132 | throw new InvalidMessageException("Bad Mac!"); 133 | } 134 | } 135 | 136 | private byte[] getMac(int messageVersion, 137 | IdentityKey senderIdentityKey, 138 | IdentityKey receiverIdentityKey, 139 | SecretKeySpec macKey, byte[] serialized) 140 | { 141 | try { 142 | Mac mac = Mac.getInstance("HmacSHA256"); 143 | mac.init(macKey); 144 | 145 | if (messageVersion >= 3) { 146 | mac.update(senderIdentityKey.getPublicKey().serialize()); 147 | mac.update(receiverIdentityKey.getPublicKey().serialize()); 148 | } 149 | 150 | byte[] fullMac = mac.doFinal(serialized); 151 | return ByteUtil.trim(fullMac, MAC_LENGTH); 152 | } catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) { 153 | throw new AssertionError(e); 154 | } 155 | } 156 | 157 | @Override 158 | public byte[] serialize() { 159 | return serialized; 160 | } 161 | 162 | @Override 163 | public int getType() { 164 | return CiphertextMessage.WHISPER_TYPE; 165 | } 166 | 167 | public static boolean isLegacy(byte[] message) { 168 | return message != null && message.length >= 1 && 169 | ByteUtil.highBitsToInt(message[0]) <= CiphertextMessage.UNSUPPORTED_VERSION; 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyState.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open Whisper Systems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package org.whispersystems.libsignal.groups.state; 18 | 19 | import com.google.protobuf.ByteString; 20 | 21 | import org.whispersystems.libsignal.InvalidKeyException; 22 | import org.whispersystems.libsignal.ecc.Curve; 23 | import org.whispersystems.libsignal.ecc.ECKeyPair; 24 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 25 | import org.whispersystems.libsignal.ecc.ECPublicKey; 26 | import org.whispersystems.libsignal.groups.ratchet.SenderChainKey; 27 | import org.whispersystems.libsignal.groups.ratchet.SenderMessageKey; 28 | import org.whispersystems.libsignal.util.guava.Optional; 29 | 30 | import java.util.Iterator; 31 | import java.util.LinkedList; 32 | import java.util.List; 33 | 34 | import static org.whispersystems.libsignal.state.StorageProtos.SenderKeyStateStructure; 35 | 36 | /** 37 | * Represents the state of an individual SenderKey ratchet. 38 | * 39 | * @author Moxie Marlinspike 40 | */ 41 | public class SenderKeyState { 42 | 43 | private static final int MAX_MESSAGE_KEYS = 2000; 44 | 45 | private SenderKeyStateStructure senderKeyStateStructure; 46 | 47 | public SenderKeyState(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { 48 | this(id, iteration, chainKey, signatureKey, Optional.absent()); 49 | } 50 | 51 | public SenderKeyState(int id, int iteration, byte[] chainKey, ECKeyPair signatureKey) { 52 | this(id, iteration, chainKey, signatureKey.getPublicKey(), Optional.of(signatureKey.getPrivateKey())); 53 | } 54 | 55 | private SenderKeyState(int id, int iteration, byte[] chainKey, 56 | ECPublicKey signatureKeyPublic, 57 | Optional signatureKeyPrivate) 58 | { 59 | SenderKeyStateStructure.SenderChainKey senderChainKeyStructure = 60 | SenderKeyStateStructure.SenderChainKey.newBuilder() 61 | .setIteration(iteration) 62 | .setSeed(ByteString.copyFrom(chainKey)) 63 | .build(); 64 | 65 | SenderKeyStateStructure.SenderSigningKey.Builder signingKeyStructure = 66 | SenderKeyStateStructure.SenderSigningKey.newBuilder() 67 | .setPublic(ByteString.copyFrom(signatureKeyPublic.serialize())); 68 | 69 | if (signatureKeyPrivate.isPresent()) { 70 | signingKeyStructure.setPrivate(ByteString.copyFrom(signatureKeyPrivate.get().serialize())); 71 | } 72 | 73 | this.senderKeyStateStructure = SenderKeyStateStructure.newBuilder() 74 | .setSenderKeyId(id) 75 | .setSenderChainKey(senderChainKeyStructure) 76 | .setSenderSigningKey(signingKeyStructure) 77 | .build(); 78 | } 79 | 80 | public SenderKeyState(SenderKeyStateStructure senderKeyStateStructure) { 81 | this.senderKeyStateStructure = senderKeyStateStructure; 82 | } 83 | 84 | public int getKeyId() { 85 | return senderKeyStateStructure.getSenderKeyId(); 86 | } 87 | 88 | public SenderChainKey getSenderChainKey() { 89 | return new SenderChainKey(senderKeyStateStructure.getSenderChainKey().getIteration(), 90 | senderKeyStateStructure.getSenderChainKey().getSeed().toByteArray()); 91 | } 92 | 93 | public void setSenderChainKey(SenderChainKey chainKey) { 94 | SenderKeyStateStructure.SenderChainKey senderChainKeyStructure = 95 | SenderKeyStateStructure.SenderChainKey.newBuilder() 96 | .setIteration(chainKey.getIteration()) 97 | .setSeed(ByteString.copyFrom(chainKey.getSeed())) 98 | .build(); 99 | 100 | this.senderKeyStateStructure = senderKeyStateStructure.toBuilder() 101 | .setSenderChainKey(senderChainKeyStructure) 102 | .build(); 103 | } 104 | 105 | public ECPublicKey getSigningKeyPublic() throws InvalidKeyException { 106 | return Curve.decodePoint(senderKeyStateStructure.getSenderSigningKey() 107 | .getPublic() 108 | .toByteArray(), 0); 109 | } 110 | 111 | public ECPrivateKey getSigningKeyPrivate() { 112 | return Curve.decodePrivatePoint(senderKeyStateStructure.getSenderSigningKey() 113 | .getPrivate().toByteArray()); 114 | } 115 | 116 | public boolean hasSenderMessageKey(int iteration) { 117 | for (SenderKeyStateStructure.SenderMessageKey senderMessageKey : senderKeyStateStructure.getSenderMessageKeysList()) { 118 | if (senderMessageKey.getIteration() == iteration) return true; 119 | } 120 | 121 | return false; 122 | } 123 | 124 | public void addSenderMessageKey(SenderMessageKey senderMessageKey) { 125 | SenderKeyStateStructure.SenderMessageKey senderMessageKeyStructure = 126 | SenderKeyStateStructure.SenderMessageKey.newBuilder() 127 | .setIteration(senderMessageKey.getIteration()) 128 | .setSeed(ByteString.copyFrom(senderMessageKey.getSeed())) 129 | .build(); 130 | 131 | SenderKeyStateStructure.Builder builder = this.senderKeyStateStructure.toBuilder(); 132 | 133 | builder.addSenderMessageKeys(senderMessageKeyStructure); 134 | 135 | if (builder.getSenderMessageKeysCount() > MAX_MESSAGE_KEYS) { 136 | builder.removeSenderMessageKeys(0); 137 | } 138 | 139 | this.senderKeyStateStructure = builder.build(); 140 | } 141 | 142 | public SenderMessageKey removeSenderMessageKey(int iteration) { 143 | List keys = new LinkedList<>(senderKeyStateStructure.getSenderMessageKeysList()); 144 | Iterator iterator = keys.iterator(); 145 | 146 | SenderKeyStateStructure.SenderMessageKey result = null; 147 | 148 | while (iterator.hasNext()) { 149 | SenderKeyStateStructure.SenderMessageKey senderMessageKey = iterator.next(); 150 | 151 | if (senderMessageKey.getIteration() == iteration) { 152 | result = senderMessageKey; 153 | iterator.remove(); 154 | break; 155 | } 156 | } 157 | 158 | this.senderKeyStateStructure = this.senderKeyStateStructure.toBuilder() 159 | .clearSenderMessageKeys() 160 | .addAllSenderMessageKeys(keys) 161 | .build(); 162 | 163 | if (result != null) { 164 | return new SenderMessageKey(result.getIteration(), result.getSeed().toByteArray()); 165 | } else { 166 | return null; 167 | } 168 | } 169 | 170 | public SenderKeyStateStructure getStructure() { 171 | return senderKeyStateStructure; 172 | } 173 | } 174 | --------------------------------------------------------------------------------