├── .gitignore ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── whispersystems │ │ └── libsignal │ │ └── CurveTest.java │ └── main │ ├── AndroidManifest.xml │ └── java │ └── org │ └── whispersystems │ └── libsignal │ └── util │ └── AndroidSignalProtocolLogger.java ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── java ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── whispersystems │ │ └── libsignal │ │ ├── DecryptionCallback.java │ │ ├── DuplicateMessageException.java │ │ ├── IdentityKey.java │ │ ├── IdentityKeyPair.java │ │ ├── InvalidKeyException.java │ │ ├── InvalidKeyIdException.java │ │ ├── InvalidMacException.java │ │ ├── InvalidMessageException.java │ │ ├── InvalidVersionException.java │ │ ├── LegacyMessageException.java │ │ ├── NoSessionException.java │ │ ├── SessionBuilder.java │ │ ├── SessionCipher.java │ │ ├── SignalProtocolAddress.java │ │ ├── StaleKeyExchangeException.java │ │ ├── UntrustedIdentityException.java │ │ ├── devices │ │ ├── DeviceConsistencyCodeGenerator.java │ │ ├── DeviceConsistencyCommitment.java │ │ └── DeviceConsistencySignature.java │ │ ├── ecc │ │ ├── Curve.java │ │ ├── DjbECPrivateKey.java │ │ ├── DjbECPublicKey.java │ │ ├── ECKeyPair.java │ │ ├── ECPrivateKey.java │ │ └── ECPublicKey.java │ │ ├── fingerprint │ │ ├── DisplayableFingerprint.java │ │ ├── Fingerprint.java │ │ ├── FingerprintGenerator.java │ │ ├── FingerprintIdentifierMismatchException.java │ │ ├── FingerprintParsingException.java │ │ ├── FingerprintVersionMismatchException.java │ │ ├── NumericFingerprintGenerator.java │ │ └── ScannableFingerprint.java │ │ ├── groups │ │ ├── GroupCipher.java │ │ ├── GroupSessionBuilder.java │ │ ├── SenderKeyName.java │ │ ├── ratchet │ │ │ ├── SenderChainKey.java │ │ │ └── SenderMessageKey.java │ │ └── state │ │ │ ├── SenderKeyRecord.java │ │ │ ├── SenderKeyState.java │ │ │ └── SenderKeyStore.java │ │ ├── kdf │ │ ├── DerivedMessageSecrets.java │ │ ├── DerivedRootSecrets.java │ │ ├── HKDF.java │ │ ├── HKDFv2.java │ │ └── HKDFv3.java │ │ ├── logging │ │ ├── Log.java │ │ ├── SignalProtocolLogger.java │ │ └── SignalProtocolLoggerProvider.java │ │ ├── protocol │ │ ├── CiphertextMessage.java │ │ ├── DeviceConsistencyMessage.java │ │ ├── PreKeySignalMessage.java │ │ ├── SenderKeyDistributionMessage.java │ │ ├── SenderKeyMessage.java │ │ └── SignalMessage.java │ │ ├── ratchet │ │ ├── AliceSignalProtocolParameters.java │ │ ├── BobSignalProtocolParameters.java │ │ ├── ChainKey.java │ │ ├── MessageKeys.java │ │ ├── RatchetingSession.java │ │ ├── RootKey.java │ │ └── SymmetricSignalProtocolParameters.java │ │ ├── state │ │ ├── IdentityKeyStore.java │ │ ├── PreKeyBundle.java │ │ ├── PreKeyRecord.java │ │ ├── PreKeyStore.java │ │ ├── SessionRecord.java │ │ ├── SessionState.java │ │ ├── SessionStore.java │ │ ├── SignalProtocolStore.java │ │ ├── SignedPreKeyRecord.java │ │ ├── SignedPreKeyStore.java │ │ └── impl │ │ │ ├── InMemoryIdentityKeyStore.java │ │ │ ├── InMemoryPreKeyStore.java │ │ │ ├── InMemorySessionStore.java │ │ │ ├── InMemorySignalProtocolStore.java │ │ │ └── InMemorySignedPreKeyStore.java │ │ └── util │ │ ├── ByteArrayComparator.java │ │ ├── ByteUtil.java │ │ ├── Hex.java │ │ ├── IdentityKeyComparator.java │ │ ├── KeyHelper.java │ │ ├── Medium.java │ │ ├── Pair.java │ │ └── guava │ │ ├── Absent.java │ │ ├── Function.java │ │ ├── Optional.java │ │ ├── Preconditions.java │ │ ├── Present.java │ │ └── Supplier.java │ └── test │ └── java │ └── org │ └── whispersystems │ └── libsignal │ └── CurveTest.java ├── protobuf ├── FingerprintProtocol.proto ├── LocalStorageProtocol.proto └── WhisperTextProtocol.proto ├── settings.gradle └── tests ├── build.gradle └── src └── test └── java └── org └── whispersystems └── libsignal ├── SessionBuilderTest.java ├── SessionCipherTest.java ├── SimultaneousInitiateTests.java ├── TestInMemoryIdentityKeyStore.java ├── TestInMemorySignalProtocolStore.java ├── devices └── DeviceConsistencyTest.java ├── ecc └── Curve25519Test.java ├── fingerprint └── NumericFingerprintGeneratorTest.java ├── groups ├── GroupCipherTest.java └── InMemorySenderKeyStore.java ├── kdf └── HKDFTest.java └── ratchet ├── ChainKeyTest.java ├── RatchetingSessionTest.java └── RootKeyTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | /obj 3 | *.iml 4 | .gradle 5 | .idea 6 | gradle.properties 7 | local.properties 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | A ratcheting forward secrecy protocol that works in synchronous and asynchronous messaging environments. 4 | 5 | ## PreKeys 6 | 7 | This protocol uses a concept called 'PreKeys'. A PreKey is an ECPublicKey and an associated unique 8 | ID which are stored together by a server. PreKeys can also be signed. 9 | 10 | At install time, clients generate a single signed PreKey, as well as a large list of unsigned 11 | PreKeys, and transmit all of them to the server. 12 | 13 | ## Sessions 14 | 15 | Signal Protocol is session-oriented. Clients establish a "session," which is then used for 16 | all subsequent encrypt/decrypt operations. There is no need to ever tear down a session once one 17 | has been established. 18 | 19 | Sessions are established in one of three ways: 20 | 21 | 1. PreKeyBundles. A client that wishes to send a message to a recipient can establish a session by 22 | retrieving a PreKeyBundle for that recipient from the server. 23 | 1. PreKeySignalMessages. A client can receive a PreKeySignalMessage from a recipient and use it 24 | to establish a session. 25 | 1. KeyExchangeMessages. Two clients can exchange KeyExchange messages to establish a session. 26 | 27 | ## State 28 | 29 | An established session encapsulates a lot of state between two clients. That state is maintained 30 | in durable records which need to be kept for the life of the session. 31 | 32 | State is kept in the following places: 33 | 34 | 1. Identity State. Clients will need to maintain the state of their own identity key pair, as well 35 | as identity keys received from other clients. 36 | 1. PreKey State. Clients will need to maintain the state of their generated PreKeys. 37 | 1. Signed PreKey States. Clients will need to maintain the state of their signed PreKeys. 38 | 1. Session State. Clients will need to maintain the state of the sessions they have established. 39 | 40 | # Using libsignal-protocol 41 | 42 | ## Configuration 43 | 44 | On Android: 45 | 46 | ```groovy 47 | dependencies { 48 | compile 'org.whispersystems:signal-protocol-android:(latest version number)' 49 | } 50 | ``` 51 | 52 | For pure Java apps: 53 | 54 | ```xml 55 | 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 | ```java 68 | IdentityKeyPair identityKeyPair = KeyHelper.generateIdentityKeyPair(); 69 | int registrationId = KeyHelper.generateRegistrationId(); 70 | List preKeys = KeyHelper.generatePreKeys(startId, 100); 71 | SignedPreKeyRecord signedPreKey = KeyHelper.generateSignedPreKey(identityKeyPair, 5); 72 | 73 | // Store identityKeyPair somewhere durable and safe. 74 | // Store registrationId somewhere durable and safe. 75 | 76 | // Store preKeys in PreKeyStore. 77 | // Store signed prekey in SignedPreKeyStore. 78 | ``` 79 | 80 | ## Building a session 81 | 82 | A libsignal client needs to implement four interfaces: IdentityKeyStore, PreKeyStore, 83 | SignedPreKeyStore, and SessionStore. These will manage loading and storing of identity, 84 | prekeys, signed prekeys, and session state. 85 | 86 | Once those are implemented, building a session is fairly straightforward: 87 | 88 | ```java 89 | SessionStore sessionStore = new MySessionStore(); 90 | PreKeyStore preKeyStore = new MyPreKeyStore(); 91 | SignedPreKeyStore signedPreKeyStore = new MySignedPreKeyStore(); 92 | IdentityKeyStore identityStore = new MyIdentityKeyStore(); 93 | 94 | // Instantiate a SessionBuilder for a remote recipientId + deviceId tuple. 95 | SessionBuilder sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore, 96 | identityStore, recipientId, deviceId); 97 | 98 | // Build a session with a PreKey retrieved from the server. 99 | sessionBuilder.process(retrievedPreKey); 100 | 101 | SessionCipher sessionCipher = new SessionCipher(sessionStore, recipientId, deviceId); 102 | CiphertextMessage message = sessionCipher.encrypt("Hello world!".getBytes("UTF-8")); 103 | 104 | deliver(message.serialize()); 105 | ``` 106 | 107 | # Legal things 108 | ## Cryptography Notice 109 | 110 | 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. 111 | 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. 112 | See for more information. 113 | 114 | 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. 115 | 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. 116 | 117 | ## License 118 | 119 | Copyright 2013-2019 Open Whisper Systems 120 | 121 | Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html 122 | 123 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | jcenter { 6 | content { 7 | includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824' 8 | } 9 | } 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.5.1' 14 | } 15 | } 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'maven' 19 | apply plugin: 'signing' 20 | 21 | archivesBaseName = "signal-protocol-android" 22 | version = version_number 23 | group = group_info 24 | 25 | android { 26 | compileSdkVersion 28 27 | buildToolsVersion '28.0.3' 28 | 29 | defaultConfig { 30 | minSdkVersion 19 31 | targetSdkVersion 28 32 | } 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_7 36 | targetCompatibility JavaVersion.VERSION_1_7 37 | } 38 | 39 | sourceSets { 40 | androidTest { 41 | java.srcDirs = ['src/androidTest/java', project(':tests').file('src/test/java')] 42 | } 43 | } 44 | } 45 | 46 | repositories { 47 | google() 48 | mavenCentral() 49 | mavenLocal() 50 | jcenter { 51 | content { 52 | includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824' 53 | } 54 | } 55 | } 56 | 57 | dependencies { 58 | compile "org.whispersystems:curve25519-android:${curve25519_version}" 59 | compile (project(':java')) { 60 | exclude group: 'org.whispersystems', module: 'curve25519-java' 61 | } 62 | } 63 | 64 | def isReleaseBuild() { 65 | return version.contains("SNAPSHOT") == false 66 | } 67 | 68 | def getReleaseRepositoryUrl() { 69 | return hasProperty('sonatypeRepo') ? sonatypeRepo 70 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 71 | } 72 | 73 | def getRepositoryUsername() { 74 | return hasProperty('whisperSonatypeUsername') ? whisperSonatypeUsername : "" 75 | } 76 | 77 | def getRepositoryPassword() { 78 | return hasProperty('whisperSonatypePassword') ? whisperSonatypePassword : "" 79 | } 80 | 81 | signing { 82 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 83 | sign configurations.archives 84 | } 85 | 86 | uploadArchives { 87 | configuration = configurations.archives 88 | repositories.mavenDeployer { 89 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 90 | 91 | repository(url: getReleaseRepositoryUrl()) { 92 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 93 | } 94 | 95 | pom.project { 96 | name 'signal-protocol-android' 97 | packaging 'aar' 98 | description 'Signal Protocol cryptography library for Android' 99 | url 'https://github.com/WhisperSystems/libsignal-android' 100 | 101 | scm { 102 | url 'scm:git@github.com:WhisperSystems/libsignal-android.git' 103 | connection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 104 | developerConnection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 105 | } 106 | 107 | licenses { 108 | license { 109 | name 'GPLv3' 110 | url 'https://www.gnu.org/licenses/gpl-3.0.txt' 111 | distribution 'repo' 112 | } 113 | } 114 | 115 | developers { 116 | developer { 117 | name 'Moxie Marlinspike' 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | task installArchives(type: Upload) { 125 | description "Installs the artifacts to the local Maven repository." 126 | configuration = configurations['archives'] 127 | repositories { 128 | mavenDeployer { 129 | repository url: "file://${System.properties['user.home']}/.m2/repository" 130 | } 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /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/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/org/whispersystems/libsignal/util/AndroidSignalProtocolLogger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.util; 7 | 8 | import android.util.Log; 9 | import android.util.SparseIntArray; 10 | 11 | import org.whispersystems.libsignal.logging.SignalProtocolLogger; 12 | 13 | public class AndroidSignalProtocolLogger implements SignalProtocolLogger { 14 | 15 | private static final SparseIntArray PRIORITY_MAP = new SparseIntArray(5) {{ 16 | put(SignalProtocolLogger.INFO, Log.INFO); 17 | put(SignalProtocolLogger.ASSERT, Log.ASSERT); 18 | put(SignalProtocolLogger.DEBUG, Log.DEBUG); 19 | put(SignalProtocolLogger.VERBOSE, Log.VERBOSE); 20 | put(SignalProtocolLogger.WARN, Log.WARN); 21 | 22 | }}; 23 | 24 | @Override 25 | public void log(int priority, String tag, String message) { 26 | int androidPriority = PRIORITY_MAP.get(priority, Log.WARN); 27 | Log.println(androidPriority, tag, message); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | ext.version_number = "2.8.1" 3 | ext.group_info = "org.whispersystems" 4 | ext.curve25519_version = "0.5.0" 5 | 6 | if (JavaVersion.current().isJava8Compatible()) { 7 | allprojects { 8 | tasks.withType(Javadoc) { 9 | options.addStringOption('Xdoclint:none', '-quiet') 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalapp/libsignal-protocol-java/fde96d22004f32a391554e4991e4e1f0a14c2d50/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Note: Check https://gradle.org/release-checksums/ before updating wrapper or distribution 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionSha256Sum=027fdd265d277bae65a0d349b6b8da02135b0b8e14ba891e26281fa877fe37a2 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10' 9 | } 10 | } 11 | 12 | apply plugin: 'java' 13 | apply plugin: 'com.google.protobuf' 14 | apply plugin: 'maven' 15 | apply plugin: 'signing' 16 | 17 | sourceCompatibility = 1.7 18 | archivesBaseName = "signal-protocol-java" 19 | version = version_number 20 | group = group_info 21 | 22 | repositories { 23 | mavenCentral() 24 | mavenLocal() 25 | } 26 | 27 | sourceSets { 28 | test { 29 | java { 30 | srcDirs = ['src/test/java/', project(':tests').file('src/test/java')] 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | compile "org.whispersystems:curve25519-java:${curve25519_version}" 37 | compile 'com.google.protobuf:protobuf-javalite:3.10.0' 38 | 39 | testCompile ('junit:junit:3.8.2') 40 | } 41 | 42 | protobuf { 43 | protoc { 44 | artifact = 'com.google.protobuf:protoc:3.10.0' 45 | } 46 | generateProtoTasks { 47 | all().each { task -> 48 | task.builtins { 49 | java { 50 | option "lite" 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | sourceSets { 58 | main.proto.srcDir '../protobuf' 59 | } 60 | 61 | 62 | test { 63 | testLogging { 64 | events 'passed' 65 | showStandardStreams = true 66 | } 67 | 68 | include 'org/whispersystems/**' 69 | } 70 | 71 | def isReleaseBuild() { 72 | return version.contains("SNAPSHOT") == false 73 | } 74 | 75 | def getReleaseRepositoryUrl() { 76 | return hasProperty('sonatypeRepo') ? sonatypeRepo 77 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 78 | } 79 | 80 | def getRepositoryUsername() { 81 | return hasProperty('whisperSonatypeUsername') ? whisperSonatypeUsername : "" 82 | } 83 | 84 | def getRepositoryPassword() { 85 | return hasProperty('whisperSonatypePassword') ? whisperSonatypePassword : "" 86 | } 87 | 88 | signing { 89 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 90 | sign configurations.archives 91 | } 92 | 93 | uploadArchives { 94 | configuration = configurations.archives 95 | repositories.mavenDeployer { 96 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 97 | 98 | repository(url: getReleaseRepositoryUrl()) { 99 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 100 | } 101 | 102 | pom.project { 103 | name 'signal-protocol-java' 104 | packaging 'jar' 105 | description 'Signal Protocol cryptography library for Java' 106 | url 'https://github.com/WhisperSystems/libsignal-android' 107 | 108 | scm { 109 | url 'scm:git@github.com:WhisperSystems/libsignal-android.git' 110 | connection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 111 | developerConnection 'scm:git@github.com:WhisperSystems/libsignal-android.git' 112 | } 113 | 114 | licenses { 115 | license { 116 | name 'GPLv3' 117 | url 'https://www.gnu.org/licenses/gpl-3.0.txt' 118 | distribution 'repo' 119 | } 120 | } 121 | 122 | developers { 123 | developer { 124 | name 'Moxie Marlinspike' 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | task installArchives(type: Upload) { 132 | description "Installs the artifacts to the local Maven repository." 133 | configuration = configurations['archives'] 134 | repositories { 135 | mavenDeployer { 136 | repository url: "file://${System.properties['user.home']}/.m2/repository" 137 | } 138 | } 139 | } 140 | 141 | task packageJavadoc(type: Jar, dependsOn: 'javadoc') { 142 | from javadoc.destinationDir 143 | classifier = 'javadoc' 144 | } 145 | 146 | task packageSources(type: Jar) { 147 | from sourceSets.main.allSource 148 | classifier = 'sources' 149 | } 150 | 151 | artifacts { 152 | archives(packageJavadoc) { 153 | type = 'javadoc' 154 | } 155 | 156 | archives packageSources 157 | } 158 | 159 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/DecryptionCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public interface DecryptionCallback { 9 | public void handlePlaintext(byte[] plaintext); 10 | } -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/DuplicateMessageException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class DuplicateMessageException extends Exception { 9 | public DuplicateMessageException(String s) { 10 | super(s); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/IdentityKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | 9 | import org.whispersystems.libsignal.ecc.Curve; 10 | import org.whispersystems.libsignal.ecc.ECPublicKey; 11 | import org.whispersystems.libsignal.util.Hex; 12 | 13 | /** 14 | * A class for representing an identity key. 15 | * 16 | * @author Moxie Marlinspike 17 | */ 18 | 19 | public class IdentityKey { 20 | 21 | private final ECPublicKey publicKey; 22 | 23 | public IdentityKey(ECPublicKey publicKey) { 24 | this.publicKey = publicKey; 25 | } 26 | 27 | public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException { 28 | this.publicKey = Curve.decodePoint(bytes, offset); 29 | } 30 | 31 | public ECPublicKey getPublicKey() { 32 | return publicKey; 33 | } 34 | 35 | public byte[] serialize() { 36 | return publicKey.serialize(); 37 | } 38 | 39 | public String getFingerprint() { 40 | return Hex.toString(publicKey.serialize()); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object other) { 45 | if (other == null) return false; 46 | if (!(other instanceof IdentityKey)) return false; 47 | 48 | return publicKey.equals(((IdentityKey) other).getPublicKey()); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return publicKey.hashCode(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/IdentityKeyPair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | import com.google.protobuf.ByteString; 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | 11 | import org.whispersystems.libsignal.ecc.Curve; 12 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 13 | 14 | import static org.whispersystems.libsignal.state.StorageProtos.IdentityKeyPairStructure; 15 | 16 | /** 17 | * Holder for public and private identity key pair. 18 | * 19 | * @author Moxie Marlinspike 20 | */ 21 | public class IdentityKeyPair { 22 | 23 | private final IdentityKey publicKey; 24 | private final ECPrivateKey privateKey; 25 | 26 | public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) { 27 | this.publicKey = publicKey; 28 | this.privateKey = privateKey; 29 | } 30 | 31 | public IdentityKeyPair(byte[] serialized) throws InvalidKeyException { 32 | try { 33 | IdentityKeyPairStructure structure = IdentityKeyPairStructure.parseFrom(serialized); 34 | this.publicKey = new IdentityKey(structure.getPublicKey().toByteArray(), 0); 35 | this.privateKey = Curve.decodePrivatePoint(structure.getPrivateKey().toByteArray()); 36 | } catch (InvalidProtocolBufferException e) { 37 | throw new InvalidKeyException(e); 38 | } 39 | } 40 | 41 | public IdentityKey getPublicKey() { 42 | return publicKey; 43 | } 44 | 45 | public ECPrivateKey getPrivateKey() { 46 | return privateKey; 47 | } 48 | 49 | public byte[] serialize() { 50 | return IdentityKeyPairStructure.newBuilder() 51 | .setPublicKey(ByteString.copyFrom(publicKey.serialize())) 52 | .setPrivateKey(ByteString.copyFrom(privateKey.serialize())) 53 | .build().toByteArray(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidKeyException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class InvalidKeyException extends Exception { 9 | 10 | public InvalidKeyException() {} 11 | 12 | public InvalidKeyException(String detailMessage) { 13 | super(detailMessage); 14 | } 15 | 16 | public InvalidKeyException(Throwable throwable) { 17 | super(throwable); 18 | } 19 | 20 | public InvalidKeyException(String detailMessage, Throwable throwable) { 21 | super(detailMessage, throwable); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidKeyIdException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class InvalidKeyIdException extends Exception { 9 | public InvalidKeyIdException(String detailMessage) { 10 | super(detailMessage); 11 | } 12 | 13 | public InvalidKeyIdException(Throwable throwable) { 14 | super(throwable); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidMacException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class InvalidMacException extends Exception { 9 | 10 | public InvalidMacException(String detailMessage) { 11 | super(detailMessage); 12 | } 13 | 14 | public InvalidMacException(Throwable throwable) { 15 | super(throwable); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidMessageException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | import java.util.List; 9 | 10 | public class InvalidMessageException extends Exception { 11 | 12 | public InvalidMessageException() {} 13 | 14 | public InvalidMessageException(String detailMessage) { 15 | super(detailMessage); 16 | } 17 | 18 | public InvalidMessageException(Throwable throwable) { 19 | super(throwable); 20 | } 21 | 22 | public InvalidMessageException(String detailMessage, Throwable throwable) { 23 | super(detailMessage, throwable); 24 | } 25 | 26 | public InvalidMessageException(String detailMessage, List exceptions) { 27 | super(detailMessage, exceptions.get(0)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/InvalidVersionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class InvalidVersionException extends Exception { 9 | public InvalidVersionException(String detailMessage) { 10 | super(detailMessage); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/LegacyMessageException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class LegacyMessageException extends Exception { 9 | public LegacyMessageException(String s) { 10 | super(s); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/NoSessionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class NoSessionException extends Exception { 9 | public NoSessionException(String s) { 10 | super(s); 11 | } 12 | 13 | public NoSessionException(Exception nested) { 14 | super(nested); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/SignalProtocolAddress.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class SignalProtocolAddress { 9 | 10 | private final String name; 11 | private final int deviceId; 12 | 13 | public SignalProtocolAddress(String name, int deviceId) { 14 | this.name = name; 15 | this.deviceId = deviceId; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public int getDeviceId() { 23 | return deviceId; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return name + ":" + deviceId; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object other) { 33 | if (other == null) return false; 34 | if (!(other instanceof SignalProtocolAddress)) return false; 35 | 36 | SignalProtocolAddress that = (SignalProtocolAddress)other; 37 | return this.name.equals(that.name) && this.deviceId == that.deviceId; 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return this.name.hashCode() ^ this.deviceId; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/StaleKeyExchangeException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class StaleKeyExchangeException extends Throwable { 9 | } 10 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/UntrustedIdentityException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal; 7 | 8 | public class UntrustedIdentityException extends Exception { 9 | 10 | private final String name; 11 | private final IdentityKey key; 12 | 13 | public UntrustedIdentityException(String name, IdentityKey key) { 14 | this.name = name; 15 | this.key = key; 16 | } 17 | 18 | public IdentityKey getUntrustedIdentity() { 19 | return key; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/devices/DeviceConsistencyCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.devices; 2 | 3 | import org.whispersystems.libsignal.util.ByteArrayComparator; 4 | import org.whispersystems.libsignal.util.ByteUtil; 5 | 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.Comparator; 11 | import java.util.List; 12 | 13 | public class DeviceConsistencyCodeGenerator { 14 | 15 | private static final int CODE_VERSION = 0; 16 | 17 | public static String generateFor(DeviceConsistencyCommitment commitment, 18 | List signatures) 19 | { 20 | try { 21 | ArrayList sortedSignatures = new ArrayList<>(signatures); 22 | Collections.sort(sortedSignatures, new SignatureComparator()); 23 | 24 | MessageDigest messageDigest = MessageDigest.getInstance("SHA-512"); 25 | messageDigest.update(ByteUtil.shortToByteArray(CODE_VERSION)); 26 | messageDigest.update(commitment.toByteArray()); 27 | 28 | for (DeviceConsistencySignature signature : sortedSignatures) { 29 | messageDigest.update(signature.getVrfOutput()); 30 | } 31 | 32 | byte[] hash = messageDigest.digest(); 33 | 34 | String digits = getEncodedChunk(hash, 0) + getEncodedChunk(hash, 5); 35 | return digits.substring(0, 6); 36 | 37 | } catch (NoSuchAlgorithmException e) { 38 | throw new AssertionError(e); 39 | } 40 | } 41 | 42 | private static String getEncodedChunk(byte[] hash, int offset) { 43 | long chunk = ByteUtil.byteArray5ToLong(hash, offset) % 100000; 44 | return String.format("%05d", chunk); 45 | } 46 | 47 | 48 | private static class SignatureComparator extends ByteArrayComparator implements Comparator { 49 | @Override 50 | public int compare(DeviceConsistencySignature first, DeviceConsistencySignature second) { 51 | return compare(first.getVrfOutput(), second.getVrfOutput()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/devices/DeviceConsistencyCommitment.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.devices; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | import org.whispersystems.libsignal.util.ByteUtil; 5 | import org.whispersystems.libsignal.util.IdentityKeyComparator; 6 | 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class DeviceConsistencyCommitment { 14 | 15 | private static final String VERSION = "DeviceConsistencyCommitment_V0"; 16 | 17 | private final int generation; 18 | private final byte[] serialized; 19 | 20 | public DeviceConsistencyCommitment(int generation, List identityKeys) { 21 | try { 22 | ArrayList sortedIdentityKeys = new ArrayList<>(identityKeys); 23 | Collections.sort(sortedIdentityKeys, new IdentityKeyComparator()); 24 | 25 | MessageDigest messageDigest = MessageDigest.getInstance("SHA-512"); 26 | messageDigest.update(VERSION.getBytes()); 27 | messageDigest.update(ByteUtil.intToByteArray(generation)); 28 | 29 | for (IdentityKey commitment : sortedIdentityKeys) { 30 | messageDigest.update(commitment.getPublicKey().serialize()); 31 | } 32 | 33 | this.generation = generation; 34 | this.serialized = messageDigest.digest(); 35 | } catch (NoSuchAlgorithmException e) { 36 | throw new AssertionError(e); 37 | } 38 | } 39 | 40 | public byte[] toByteArray() { 41 | return serialized; 42 | } 43 | 44 | public int getGeneration() { 45 | return generation; 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/devices/DeviceConsistencySignature.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.devices; 2 | 3 | public class DeviceConsistencySignature { 4 | 5 | private final byte[] signature; 6 | private final byte[] vrfOutput; 7 | 8 | public DeviceConsistencySignature(byte[] signature, byte[] vrfOutput) { 9 | this.signature = signature; 10 | this.vrfOutput = vrfOutput; 11 | } 12 | 13 | public byte[] getVrfOutput() { 14 | return vrfOutput; 15 | } 16 | 17 | public byte[] getSignature() { 18 | return signature; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/Curve.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ecc; 7 | 8 | import org.whispersystems.curve25519.Curve25519; 9 | import org.whispersystems.curve25519.Curve25519KeyPair; 10 | import org.whispersystems.curve25519.VrfSignatureVerificationFailedException; 11 | import org.whispersystems.libsignal.InvalidKeyException; 12 | 13 | import static org.whispersystems.curve25519.Curve25519.BEST; 14 | 15 | public class Curve { 16 | 17 | public static final int DJB_TYPE = 0x05; 18 | 19 | public static boolean isNative() { 20 | return Curve25519.getInstance(BEST).isNative(); 21 | } 22 | 23 | public static ECKeyPair generateKeyPair() { 24 | Curve25519KeyPair keyPair = Curve25519.getInstance(BEST).generateKeyPair(); 25 | 26 | return new ECKeyPair(new DjbECPublicKey(keyPair.getPublicKey()), 27 | new DjbECPrivateKey(keyPair.getPrivateKey())); 28 | } 29 | 30 | public static ECPublicKey decodePoint(byte[] bytes, int offset) 31 | throws InvalidKeyException 32 | { 33 | if (bytes == null || bytes.length - offset < 1) { 34 | throw new InvalidKeyException("No key type identifier"); 35 | } 36 | 37 | int type = bytes[offset] & 0xFF; 38 | 39 | switch (type) { 40 | case Curve.DJB_TYPE: 41 | if (bytes.length - offset < 33) { 42 | throw new InvalidKeyException("Bad key length: " + bytes.length); 43 | } 44 | 45 | byte[] keyBytes = new byte[32]; 46 | System.arraycopy(bytes, offset+1, keyBytes, 0, keyBytes.length); 47 | return new DjbECPublicKey(keyBytes); 48 | default: 49 | throw new InvalidKeyException("Bad key type: " + type); 50 | } 51 | } 52 | 53 | public static ECPrivateKey decodePrivatePoint(byte[] bytes) { 54 | return new DjbECPrivateKey(bytes); 55 | } 56 | 57 | public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) 58 | throws InvalidKeyException 59 | { 60 | if (publicKey == null) { 61 | throw new InvalidKeyException("public value is null"); 62 | } 63 | 64 | if (privateKey == null) { 65 | throw new InvalidKeyException("private value is null"); 66 | } 67 | 68 | if (publicKey.getType() != privateKey.getType()) { 69 | throw new InvalidKeyException("Public and private keys must be of the same type!"); 70 | } 71 | 72 | if (publicKey.getType() == DJB_TYPE) { 73 | return Curve25519.getInstance(BEST) 74 | .calculateAgreement(((DjbECPublicKey) publicKey).getPublicKey(), 75 | ((DjbECPrivateKey) privateKey).getPrivateKey()); 76 | } else { 77 | throw new InvalidKeyException("Unknown type: " + publicKey.getType()); 78 | } 79 | } 80 | 81 | public static boolean verifySignature(ECPublicKey signingKey, byte[] message, byte[] signature) 82 | throws InvalidKeyException 83 | { 84 | if (signingKey == null || message == null || signature == null) { 85 | throw new InvalidKeyException("Values must not be null"); 86 | } 87 | 88 | if (signingKey.getType() == DJB_TYPE) { 89 | return Curve25519.getInstance(BEST) 90 | .verifySignature(((DjbECPublicKey) signingKey).getPublicKey(), message, signature); 91 | } else { 92 | throw new InvalidKeyException("Unknown type: " + signingKey.getType()); 93 | } 94 | } 95 | 96 | public static byte[] calculateSignature(ECPrivateKey signingKey, byte[] message) 97 | throws InvalidKeyException 98 | { 99 | if (signingKey == null || message == null) { 100 | throw new InvalidKeyException("Values must not be null"); 101 | } 102 | 103 | if (signingKey.getType() == DJB_TYPE) { 104 | return Curve25519.getInstance(BEST) 105 | .calculateSignature(((DjbECPrivateKey) signingKey).getPrivateKey(), message); 106 | } else { 107 | throw new InvalidKeyException("Unknown type: " + signingKey.getType()); 108 | } 109 | } 110 | 111 | public static byte[] calculateVrfSignature(ECPrivateKey signingKey, byte[] message) 112 | throws InvalidKeyException 113 | { 114 | if (signingKey == null || message == null) { 115 | throw new InvalidKeyException("Values must not be null"); 116 | } 117 | 118 | if (signingKey.getType() == DJB_TYPE) { 119 | return Curve25519.getInstance(BEST) 120 | .calculateVrfSignature(((DjbECPrivateKey)signingKey).getPrivateKey(), message); 121 | } else { 122 | throw new InvalidKeyException("Unknown type: " + signingKey.getType()); 123 | } 124 | } 125 | 126 | public static byte[] verifyVrfSignature(ECPublicKey signingKey, byte[] message, byte[] signature) 127 | throws InvalidKeyException, VrfSignatureVerificationFailedException 128 | { 129 | if (signingKey == null || message == null || signature == null) { 130 | throw new InvalidKeyException("Values must not be null"); 131 | } 132 | 133 | if (signingKey.getType() == DJB_TYPE) { 134 | return Curve25519.getInstance(BEST) 135 | .verifyVrfSignature(((DjbECPublicKey) signingKey).getPublicKey(), message, signature); 136 | } else { 137 | throw new InvalidKeyException("Unknown type: " + signingKey.getType()); 138 | } 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/DjbECPrivateKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.ecc; 8 | 9 | public class DjbECPrivateKey implements ECPrivateKey { 10 | 11 | private final byte[] privateKey; 12 | 13 | DjbECPrivateKey(byte[] privateKey) { 14 | this.privateKey = privateKey; 15 | } 16 | 17 | @Override 18 | public byte[] serialize() { 19 | return privateKey; 20 | } 21 | 22 | @Override 23 | public int getType() { 24 | return Curve.DJB_TYPE; 25 | } 26 | 27 | public byte[] getPrivateKey() { 28 | return privateKey; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/DjbECPublicKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.ecc; 8 | 9 | import org.whispersystems.libsignal.util.ByteUtil; 10 | 11 | import java.math.BigInteger; 12 | import java.util.Arrays; 13 | 14 | public class DjbECPublicKey implements ECPublicKey { 15 | 16 | private final byte[] publicKey; 17 | 18 | DjbECPublicKey(byte[] publicKey) { 19 | this.publicKey = publicKey; 20 | } 21 | 22 | @Override 23 | public byte[] serialize() { 24 | byte[] type = {Curve.DJB_TYPE}; 25 | return ByteUtil.combine(type, publicKey); 26 | } 27 | 28 | @Override 29 | public int getType() { 30 | return Curve.DJB_TYPE; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object other) { 35 | if (other == null) return false; 36 | if (!(other instanceof DjbECPublicKey)) return false; 37 | 38 | DjbECPublicKey that = (DjbECPublicKey)other; 39 | return Arrays.equals(this.publicKey, that.publicKey); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Arrays.hashCode(publicKey); 45 | } 46 | 47 | @Override 48 | public int compareTo(ECPublicKey another) { 49 | return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey)); 50 | } 51 | 52 | public byte[] getPublicKey() { 53 | return publicKey; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/ECKeyPair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ecc; 7 | 8 | public class ECKeyPair { 9 | 10 | private final ECPublicKey publicKey; 11 | private final ECPrivateKey privateKey; 12 | 13 | public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) { 14 | this.publicKey = publicKey; 15 | this.privateKey = privateKey; 16 | } 17 | 18 | public ECPublicKey getPublicKey() { 19 | return publicKey; 20 | } 21 | 22 | public ECPrivateKey getPrivateKey() { 23 | return privateKey; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/ECPrivateKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.ecc; 8 | 9 | public interface ECPrivateKey { 10 | public byte[] serialize(); 11 | public int getType(); 12 | } 13 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ecc/ECPublicKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.ecc; 8 | 9 | public interface ECPublicKey extends Comparable { 10 | 11 | public static final int KEY_SIZE = 33; 12 | 13 | public byte[] serialize(); 14 | 15 | public int getType(); 16 | } 17 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/DisplayableFingerprint.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | import org.whispersystems.libsignal.util.ByteUtil; 9 | 10 | public class DisplayableFingerprint { 11 | 12 | private final String localFingerprintNumbers; 13 | private final String remoteFingerprintNumbers; 14 | 15 | DisplayableFingerprint(byte[] localFingerprint, byte[] remoteFingerprint) 16 | { 17 | this.localFingerprintNumbers = getDisplayStringFor(localFingerprint); 18 | this.remoteFingerprintNumbers = getDisplayStringFor(remoteFingerprint); 19 | } 20 | 21 | public String getDisplayText() { 22 | if (localFingerprintNumbers.compareTo(remoteFingerprintNumbers) <= 0) { 23 | return localFingerprintNumbers + remoteFingerprintNumbers; 24 | } else { 25 | return remoteFingerprintNumbers + localFingerprintNumbers; 26 | } 27 | } 28 | 29 | private String getDisplayStringFor(byte[] fingerprint) { 30 | return getEncodedChunk(fingerprint, 0) + 31 | getEncodedChunk(fingerprint, 5) + 32 | getEncodedChunk(fingerprint, 10) + 33 | getEncodedChunk(fingerprint, 15) + 34 | getEncodedChunk(fingerprint, 20) + 35 | getEncodedChunk(fingerprint, 25); 36 | } 37 | 38 | private String getEncodedChunk(byte[] hash, int offset) { 39 | long chunk = ByteUtil.byteArray5ToLong(hash, offset) % 100000; 40 | return String.format("%05d", chunk); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/Fingerprint.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | public class Fingerprint { 9 | 10 | private final DisplayableFingerprint displayableFingerprint; 11 | private final ScannableFingerprint scannableFingerprint; 12 | 13 | public Fingerprint(DisplayableFingerprint displayableFingerprint, 14 | ScannableFingerprint scannableFingerprint) 15 | { 16 | this.displayableFingerprint = displayableFingerprint; 17 | this.scannableFingerprint = scannableFingerprint; 18 | } 19 | 20 | /** 21 | * @return A text fingerprint that can be displayed and compared remotely. 22 | */ 23 | public DisplayableFingerprint getDisplayableFingerprint() { 24 | return displayableFingerprint; 25 | } 26 | 27 | /** 28 | * @return A scannable fingerprint that can be scanned and compared locally. 29 | */ 30 | public ScannableFingerprint getScannableFingerprint() { 31 | return scannableFingerprint; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | 10 | import java.util.List; 11 | 12 | public interface FingerprintGenerator { 13 | public Fingerprint createFor(int version, 14 | byte[] localStableIdentifier, 15 | IdentityKey localIdentityKey, 16 | byte[] remoteStableIdentifier, 17 | IdentityKey remoteIdentityKey); 18 | 19 | public Fingerprint createFor(int version, 20 | byte[] localStableIdentifier, 21 | List localIdentityKey, 22 | byte[] remoteStableIdentifier, 23 | List remoteIdentityKey); 24 | } 25 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintIdentifierMismatchException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | public class FingerprintIdentifierMismatchException extends Exception { 9 | 10 | private final String localIdentifier; 11 | private final String remoteIdentifier; 12 | private final String scannedLocalIdentifier; 13 | private final String scannedRemoteIdentifier; 14 | 15 | public FingerprintIdentifierMismatchException(String localIdentifier, String remoteIdentifier, 16 | String scannedLocalIdentifier, String scannedRemoteIdentifier) 17 | { 18 | this.localIdentifier = localIdentifier; 19 | this.remoteIdentifier = remoteIdentifier; 20 | this.scannedLocalIdentifier = scannedLocalIdentifier; 21 | this.scannedRemoteIdentifier = scannedRemoteIdentifier; 22 | } 23 | 24 | public String getScannedRemoteIdentifier() { 25 | return scannedRemoteIdentifier; 26 | } 27 | 28 | public String getScannedLocalIdentifier() { 29 | return scannedLocalIdentifier; 30 | } 31 | 32 | public String getRemoteIdentifier() { 33 | return remoteIdentifier; 34 | } 35 | 36 | public String getLocalIdentifier() { 37 | return localIdentifier; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintParsingException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | public class FingerprintParsingException extends Exception { 9 | 10 | public FingerprintParsingException(Exception nested) { 11 | super(nested); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintVersionMismatchException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | public class FingerprintVersionMismatchException extends Exception { 9 | 10 | private final int theirVersion; 11 | private final int ourVersion; 12 | 13 | public FingerprintVersionMismatchException(int theirVersion, int ourVersion) { 14 | super(); 15 | this.theirVersion = theirVersion; 16 | this.ourVersion = ourVersion; 17 | } 18 | 19 | public int getTheirVersion() { 20 | return theirVersion; 21 | } 22 | 23 | public int getOurVersion() { 24 | return ourVersion; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.util.ByteUtil; 10 | import org.whispersystems.libsignal.util.IdentityKeyComparator; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.security.MessageDigest; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | 20 | public class NumericFingerprintGenerator implements FingerprintGenerator { 21 | 22 | private static final int FINGERPRINT_VERSION = 0; 23 | 24 | private final int iterations; 25 | 26 | /** 27 | * Construct a fingerprint generator for 60 digit numerics. 28 | * 29 | * @param iterations The number of internal iterations to perform in the process of 30 | * generating a fingerprint. This needs to be constant, and synchronized 31 | * across all clients. 32 | * 33 | * The higher the iteration count, the higher the security level: 34 | * 35 | * - 1024 ~ 109.7 bits 36 | * - 1400 > 110 bits 37 | * - 5200 > 112 bits 38 | */ 39 | public NumericFingerprintGenerator(int iterations) { 40 | this.iterations = iterations; 41 | } 42 | 43 | /** 44 | * Generate a scannable and displayable fingerprint. 45 | * 46 | * @param version The version of fingerprint you are generating. 47 | * @param localStableIdentifier The client's "stable" identifier. 48 | * @param localIdentityKey The client's identity key. 49 | * @param remoteStableIdentifier The remote party's "stable" identifier. 50 | * @param remoteIdentityKey The remote party's identity key. 51 | * @return A unique fingerprint for this conversation. 52 | */ 53 | @Override 54 | public Fingerprint createFor(int version, 55 | byte[] localStableIdentifier, 56 | final IdentityKey localIdentityKey, 57 | byte[] remoteStableIdentifier, 58 | final IdentityKey remoteIdentityKey) 59 | { 60 | return createFor(version, 61 | localStableIdentifier, 62 | new LinkedList() {{ 63 | add(localIdentityKey); 64 | }}, 65 | remoteStableIdentifier, 66 | new LinkedList() {{ 67 | add(remoteIdentityKey); 68 | }}); 69 | } 70 | 71 | /** 72 | * Generate a scannable and displayable fingerprint for logical identities that have multiple 73 | * physical keys. 74 | * 75 | * Do not trust the output of this unless you've been through the device consistency process 76 | * for the provided localIdentityKeys. 77 | * 78 | * @param version The version of fingerprint you are generating. 79 | * @param localStableIdentifier The client's "stable" identifier. 80 | * @param localIdentityKeys The client's collection of physical identity keys. 81 | * @param remoteStableIdentifier The remote party's "stable" identifier. 82 | * @param remoteIdentityKeys The remote party's collection of physical identity key. 83 | * @return A unique fingerprint for this conversation. 84 | */ 85 | public Fingerprint createFor(int version, 86 | byte[] localStableIdentifier, 87 | List localIdentityKeys, 88 | byte[] remoteStableIdentifier, 89 | List remoteIdentityKeys) 90 | { 91 | byte[] localFingerprint = getFingerprint(iterations, localStableIdentifier, localIdentityKeys); 92 | byte[] remoteFingerprint = getFingerprint(iterations, remoteStableIdentifier, remoteIdentityKeys); 93 | 94 | DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(localFingerprint, 95 | remoteFingerprint); 96 | 97 | ScannableFingerprint scannableFingerprint = new ScannableFingerprint(version, 98 | localFingerprint, 99 | remoteFingerprint); 100 | 101 | return new Fingerprint(displayableFingerprint, scannableFingerprint); 102 | } 103 | 104 | private byte[] getFingerprint(int iterations, byte[] stableIdentifier, List unsortedIdentityKeys) { 105 | try { 106 | MessageDigest digest = MessageDigest.getInstance("SHA-512"); 107 | byte[] publicKey = getLogicalKeyBytes(unsortedIdentityKeys); 108 | byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(FINGERPRINT_VERSION), 109 | publicKey, stableIdentifier); 110 | 111 | for (int i=0;i identityKeys) { 123 | ArrayList sortedIdentityKeys = new ArrayList<>(identityKeys); 124 | Collections.sort(sortedIdentityKeys, new IdentityKeyComparator()); 125 | 126 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 127 | 128 | for (IdentityKey identityKey : sortedIdentityKeys) { 129 | byte[] publicKeyBytes = identityKey.getPublicKey().serialize(); 130 | baos.write(publicKeyBytes, 0, publicKeyBytes.length); 131 | } 132 | 133 | return baos.toByteArray(); 134 | } 135 | 136 | 137 | } 138 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/fingerprint/ScannableFingerprint.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.fingerprint; 7 | 8 | import com.google.protobuf.ByteString; 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | 11 | import org.whispersystems.libsignal.fingerprint.FingerprintProtos.CombinedFingerprints; 12 | import org.whispersystems.libsignal.fingerprint.FingerprintProtos.LogicalFingerprint; 13 | import org.whispersystems.libsignal.util.ByteUtil; 14 | 15 | import java.security.MessageDigest; 16 | 17 | public class ScannableFingerprint { 18 | 19 | private final int version; 20 | private final CombinedFingerprints fingerprints; 21 | 22 | ScannableFingerprint(int version, byte[] localFingerprintData, byte[] remoteFingerprintData) 23 | { 24 | LogicalFingerprint localFingerprint = LogicalFingerprint.newBuilder() 25 | .setContent(ByteString.copyFrom(ByteUtil.trim(localFingerprintData, 32))) 26 | .build(); 27 | 28 | LogicalFingerprint remoteFingerprint = LogicalFingerprint.newBuilder() 29 | .setContent(ByteString.copyFrom(ByteUtil.trim(remoteFingerprintData, 32))) 30 | .build(); 31 | 32 | this.version = version; 33 | this.fingerprints = CombinedFingerprints.newBuilder() 34 | .setVersion(version) 35 | .setLocalFingerprint(localFingerprint) 36 | .setRemoteFingerprint(remoteFingerprint) 37 | .build(); 38 | } 39 | 40 | /** 41 | * @return A byte string to be displayed in a QR code. 42 | */ 43 | public byte[] getSerialized() { 44 | return fingerprints.toByteArray(); 45 | } 46 | 47 | /** 48 | * Compare a scanned QR code with what we expect. 49 | * 50 | * @param scannedFingerprintData The scanned data 51 | * @return True if matching, otherwise false. 52 | * @throws FingerprintVersionMismatchException if the scanned fingerprint is the wrong version. 53 | */ 54 | public boolean compareTo(byte[] scannedFingerprintData) 55 | throws FingerprintVersionMismatchException, 56 | FingerprintParsingException 57 | { 58 | try { 59 | CombinedFingerprints scanned = CombinedFingerprints.parseFrom(scannedFingerprintData); 60 | 61 | if (!scanned.hasRemoteFingerprint() || !scanned.hasLocalFingerprint() || 62 | !scanned.hasVersion() || scanned.getVersion() != version) 63 | { 64 | throw new FingerprintVersionMismatchException(scanned.getVersion(), version); 65 | } 66 | 67 | return MessageDigest.isEqual(fingerprints.getLocalFingerprint().getContent().toByteArray(), scanned.getRemoteFingerprint().getContent().toByteArray()) && 68 | MessageDigest.isEqual(fingerprints.getRemoteFingerprint().getContent().toByteArray(), scanned.getLocalFingerprint().getContent().toByteArray()); 69 | } catch (InvalidProtocolBufferException e) { 70 | throw new FingerprintParsingException(e); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/GroupSessionBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.groups; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyException; 9 | import org.whispersystems.libsignal.InvalidKeyIdException; 10 | import org.whispersystems.libsignal.groups.state.SenderKeyRecord; 11 | import org.whispersystems.libsignal.groups.state.SenderKeyState; 12 | import org.whispersystems.libsignal.groups.state.SenderKeyStore; 13 | import org.whispersystems.libsignal.protocol.SenderKeyDistributionMessage; 14 | import org.whispersystems.libsignal.util.KeyHelper; 15 | 16 | /** 17 | * GroupSessionBuilder is responsible for setting up group SenderKey encrypted sessions. 18 | * 19 | * Once a session has been established, {@link org.whispersystems.libsignal.groups.GroupCipher} 20 | * can be used to encrypt/decrypt messages in that session. 21 | *

22 | * The built sessions are unidirectional: they can be used either for sending or for receiving, 23 | * but not both. 24 | * 25 | * Sessions are constructed per (groupId + senderId + deviceId) tuple. Remote logical users 26 | * are identified by their senderId, and each logical recipientId can have multiple physical 27 | * devices. 28 | * 29 | * @author Moxie Marlinspike 30 | */ 31 | 32 | public class GroupSessionBuilder { 33 | 34 | private final SenderKeyStore senderKeyStore; 35 | 36 | public GroupSessionBuilder(SenderKeyStore senderKeyStore) { 37 | this.senderKeyStore = senderKeyStore; 38 | } 39 | 40 | /** 41 | * Construct a group session for receiving messages from senderKeyName. 42 | * 43 | * @param senderKeyName The (groupId, senderId, deviceId) tuple associated with the SenderKeyDistributionMessage. 44 | * @param senderKeyDistributionMessage A received SenderKeyDistributionMessage. 45 | */ 46 | public void process(SenderKeyName senderKeyName, SenderKeyDistributionMessage senderKeyDistributionMessage) { 47 | synchronized (GroupCipher.LOCK) { 48 | SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName); 49 | senderKeyRecord.addSenderKeyState(senderKeyDistributionMessage.getId(), 50 | senderKeyDistributionMessage.getIteration(), 51 | senderKeyDistributionMessage.getChainKey(), 52 | senderKeyDistributionMessage.getSignatureKey()); 53 | senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); 54 | } 55 | } 56 | 57 | /** 58 | * Construct a group session for sending messages. 59 | * 60 | * @param senderKeyName The (groupId, senderId, deviceId) tuple. In this case, 'senderId' should be the caller. 61 | * @return A SenderKeyDistributionMessage that is individually distributed to each member of the group. 62 | */ 63 | public SenderKeyDistributionMessage create(SenderKeyName senderKeyName) { 64 | synchronized (GroupCipher.LOCK) { 65 | try { 66 | SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName); 67 | 68 | if (senderKeyRecord.isEmpty()) { 69 | senderKeyRecord.setSenderKeyState(KeyHelper.generateSenderKeyId(), 70 | 0, 71 | KeyHelper.generateSenderKey(), 72 | KeyHelper.generateSenderSigningKey()); 73 | senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); 74 | } 75 | 76 | SenderKeyState state = senderKeyRecord.getSenderKeyState(); 77 | 78 | return new SenderKeyDistributionMessage(state.getKeyId(), 79 | state.getSenderChainKey().getIteration(), 80 | state.getSenderChainKey().getSeed(), 81 | state.getSigningKeyPublic()); 82 | 83 | } catch (InvalidKeyIdException | InvalidKeyException e) { 84 | throw new AssertionError(e); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/SenderKeyName.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.groups; 7 | 8 | import org.whispersystems.libsignal.SignalProtocolAddress; 9 | 10 | /** 11 | * A representation of a (groupId + senderId + deviceId) tuple. 12 | */ 13 | public class SenderKeyName { 14 | 15 | private final String groupId; 16 | private final SignalProtocolAddress sender; 17 | 18 | public SenderKeyName(String groupId, SignalProtocolAddress sender) { 19 | this.groupId = groupId; 20 | this.sender = sender; 21 | } 22 | 23 | public String getGroupId() { 24 | return groupId; 25 | } 26 | 27 | public SignalProtocolAddress getSender() { 28 | return sender; 29 | } 30 | 31 | public String serialize() { 32 | return groupId + "::" + sender.getName() + "::" + String.valueOf(sender.getDeviceId()); 33 | } 34 | 35 | @Override 36 | public boolean equals(Object other) { 37 | if (other == null) return false; 38 | if (!(other instanceof SenderKeyName)) return false; 39 | 40 | SenderKeyName that = (SenderKeyName)other; 41 | 42 | return 43 | this.groupId.equals(that.groupId) && 44 | this.sender.equals(that.sender); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return this.groupId.hashCode() ^ this.sender.hashCode(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/ratchet/SenderChainKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.groups.ratchet; 7 | 8 | import java.security.InvalidKeyException; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | import javax.crypto.Mac; 12 | import javax.crypto.spec.SecretKeySpec; 13 | 14 | /** 15 | * Each SenderKey is a "chain" of keys, each derived from the previous. 16 | * 17 | * At any given point in time, the state of a SenderKey can be represented 18 | * as the current chain key value, along with its iteration count. From there, 19 | * subsequent iterations can be derived, as well as individual message keys from 20 | * each chain key. 21 | * 22 | * @author Moxie Marlinspike 23 | */ 24 | public class SenderChainKey { 25 | 26 | private static final byte[] MESSAGE_KEY_SEED = {0x01}; 27 | private static final byte[] CHAIN_KEY_SEED = {0x02}; 28 | 29 | private final int iteration; 30 | private final byte[] chainKey; 31 | 32 | public SenderChainKey(int iteration, byte[] chainKey) { 33 | this.iteration = iteration; 34 | this.chainKey = chainKey; 35 | } 36 | 37 | public int getIteration() { 38 | return iteration; 39 | } 40 | 41 | public SenderMessageKey getSenderMessageKey() { 42 | return new SenderMessageKey(iteration, getDerivative(MESSAGE_KEY_SEED, chainKey)); 43 | } 44 | 45 | public SenderChainKey getNext() { 46 | return new SenderChainKey(iteration + 1, getDerivative(CHAIN_KEY_SEED, chainKey)); 47 | } 48 | 49 | public byte[] getSeed() { 50 | return chainKey; 51 | } 52 | 53 | private byte[] getDerivative(byte[] seed, byte[] key) { 54 | try { 55 | Mac mac = Mac.getInstance("HmacSHA256"); 56 | mac.init(new SecretKeySpec(key, "HmacSHA256")); 57 | 58 | return mac.doFinal(seed); 59 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 60 | throw new AssertionError(e); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/ratchet/SenderMessageKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.groups.ratchet; 8 | 9 | import org.whispersystems.libsignal.kdf.HKDFv3; 10 | import org.whispersystems.libsignal.util.ByteUtil; 11 | 12 | /** 13 | * The final symmetric material (IV and Cipher Key) used for encrypting 14 | * individual SenderKey messages. 15 | * 16 | * @author Moxie Marlinspike 17 | */ 18 | public class SenderMessageKey { 19 | 20 | private final int iteration; 21 | private final byte[] iv; 22 | private final byte[] cipherKey; 23 | private final byte[] seed; 24 | 25 | public SenderMessageKey(int iteration, byte[] seed) { 26 | byte[] derivative = new HKDFv3().deriveSecrets(seed, "WhisperGroup".getBytes(), 48); 27 | byte[][] parts = ByteUtil.split(derivative, 16, 32); 28 | 29 | this.iteration = iteration; 30 | this.seed = seed; 31 | this.iv = parts[0]; 32 | this.cipherKey = parts[1]; 33 | } 34 | 35 | public int getIteration() { 36 | return iteration; 37 | } 38 | 39 | public byte[] getIv() { 40 | return iv; 41 | } 42 | 43 | public byte[] getCipherKey() { 44 | return cipherKey; 45 | } 46 | 47 | public byte[] getSeed() { 48 | return seed; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyRecord.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.groups.state; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyIdException; 9 | import org.whispersystems.libsignal.ecc.ECKeyPair; 10 | import org.whispersystems.libsignal.ecc.ECPublicKey; 11 | import org.whispersystems.libsignal.state.StorageProtos; 12 | 13 | import java.io.IOException; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | import static org.whispersystems.libsignal.state.StorageProtos.SenderKeyRecordStructure; 18 | 19 | /** 20 | * A durable representation of a set of SenderKeyStates for a specific 21 | * SenderKeyName. 22 | * 23 | * @author Moxie Marlinspike 24 | */ 25 | public class SenderKeyRecord { 26 | 27 | private static final int MAX_STATES = 5; 28 | 29 | private LinkedList senderKeyStates = new LinkedList<>(); 30 | 31 | public SenderKeyRecord() {} 32 | 33 | public SenderKeyRecord(byte[] serialized) throws IOException { 34 | SenderKeyRecordStructure senderKeyRecordStructure = SenderKeyRecordStructure.parseFrom(serialized); 35 | 36 | for (StorageProtos.SenderKeyStateStructure structure : senderKeyRecordStructure.getSenderKeyStatesList()) { 37 | this.senderKeyStates.add(new SenderKeyState(structure)); 38 | } 39 | } 40 | 41 | public boolean isEmpty() { 42 | return senderKeyStates.isEmpty(); 43 | } 44 | 45 | public SenderKeyState getSenderKeyState() throws InvalidKeyIdException { 46 | if (!senderKeyStates.isEmpty()) { 47 | return senderKeyStates.get(0); 48 | } else { 49 | throw new InvalidKeyIdException("No key state in record!"); 50 | } 51 | } 52 | 53 | public SenderKeyState getSenderKeyState(int keyId) throws InvalidKeyIdException { 54 | for (SenderKeyState state : senderKeyStates) { 55 | if (state.getKeyId() == keyId) { 56 | return state; 57 | } 58 | } 59 | 60 | throw new InvalidKeyIdException("No keys for: " + keyId); 61 | } 62 | 63 | public void addSenderKeyState(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { 64 | senderKeyStates.addFirst(new SenderKeyState(id, iteration, chainKey, signatureKey)); 65 | 66 | if (senderKeyStates.size() > MAX_STATES) { 67 | senderKeyStates.removeLast(); 68 | } 69 | } 70 | 71 | public void setSenderKeyState(int id, int iteration, byte[] chainKey, ECKeyPair signatureKey) { 72 | senderKeyStates.clear(); 73 | senderKeyStates.add(new SenderKeyState(id, iteration, chainKey, signatureKey)); 74 | } 75 | 76 | public byte[] serialize() { 77 | SenderKeyRecordStructure.Builder recordStructure = SenderKeyRecordStructure.newBuilder(); 78 | 79 | for (SenderKeyState senderKeyState : senderKeyStates) { 80 | recordStructure.addSenderKeyStates(senderKeyState.getStructure()); 81 | } 82 | 83 | return recordStructure.build().toByteArray(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyState.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.groups.state; 7 | 8 | import com.google.protobuf.ByteString; 9 | 10 | import org.whispersystems.libsignal.InvalidKeyException; 11 | import org.whispersystems.libsignal.ecc.Curve; 12 | import org.whispersystems.libsignal.ecc.ECKeyPair; 13 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 14 | import org.whispersystems.libsignal.ecc.ECPublicKey; 15 | import org.whispersystems.libsignal.groups.ratchet.SenderChainKey; 16 | import org.whispersystems.libsignal.groups.ratchet.SenderMessageKey; 17 | import org.whispersystems.libsignal.util.guava.Optional; 18 | 19 | import java.util.Iterator; 20 | import java.util.LinkedList; 21 | import java.util.List; 22 | 23 | import static org.whispersystems.libsignal.state.StorageProtos.SenderKeyStateStructure; 24 | 25 | /** 26 | * Represents the state of an individual SenderKey ratchet. 27 | * 28 | * @author Moxie Marlinspike 29 | */ 30 | public class SenderKeyState { 31 | 32 | private static final int MAX_MESSAGE_KEYS = 2000; 33 | 34 | private SenderKeyStateStructure senderKeyStateStructure; 35 | 36 | public SenderKeyState(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { 37 | this(id, iteration, chainKey, signatureKey, Optional.absent()); 38 | } 39 | 40 | public SenderKeyState(int id, int iteration, byte[] chainKey, ECKeyPair signatureKey) { 41 | this(id, iteration, chainKey, signatureKey.getPublicKey(), Optional.of(signatureKey.getPrivateKey())); 42 | } 43 | 44 | private SenderKeyState(int id, int iteration, byte[] chainKey, 45 | ECPublicKey signatureKeyPublic, 46 | Optional signatureKeyPrivate) 47 | { 48 | SenderKeyStateStructure.SenderChainKey senderChainKeyStructure = 49 | SenderKeyStateStructure.SenderChainKey.newBuilder() 50 | .setIteration(iteration) 51 | .setSeed(ByteString.copyFrom(chainKey)) 52 | .build(); 53 | 54 | SenderKeyStateStructure.SenderSigningKey.Builder signingKeyStructure = 55 | SenderKeyStateStructure.SenderSigningKey.newBuilder() 56 | .setPublic(ByteString.copyFrom(signatureKeyPublic.serialize())); 57 | 58 | if (signatureKeyPrivate.isPresent()) { 59 | signingKeyStructure.setPrivate(ByteString.copyFrom(signatureKeyPrivate.get().serialize())); 60 | } 61 | 62 | this.senderKeyStateStructure = SenderKeyStateStructure.newBuilder() 63 | .setSenderKeyId(id) 64 | .setSenderChainKey(senderChainKeyStructure) 65 | .setSenderSigningKey(signingKeyStructure) 66 | .build(); 67 | } 68 | 69 | public SenderKeyState(SenderKeyStateStructure senderKeyStateStructure) { 70 | this.senderKeyStateStructure = senderKeyStateStructure; 71 | } 72 | 73 | public int getKeyId() { 74 | return senderKeyStateStructure.getSenderKeyId(); 75 | } 76 | 77 | public SenderChainKey getSenderChainKey() { 78 | return new SenderChainKey(senderKeyStateStructure.getSenderChainKey().getIteration(), 79 | senderKeyStateStructure.getSenderChainKey().getSeed().toByteArray()); 80 | } 81 | 82 | public void setSenderChainKey(SenderChainKey chainKey) { 83 | SenderKeyStateStructure.SenderChainKey senderChainKeyStructure = 84 | SenderKeyStateStructure.SenderChainKey.newBuilder() 85 | .setIteration(chainKey.getIteration()) 86 | .setSeed(ByteString.copyFrom(chainKey.getSeed())) 87 | .build(); 88 | 89 | this.senderKeyStateStructure = senderKeyStateStructure.toBuilder() 90 | .setSenderChainKey(senderChainKeyStructure) 91 | .build(); 92 | } 93 | 94 | public ECPublicKey getSigningKeyPublic() throws InvalidKeyException { 95 | return Curve.decodePoint(senderKeyStateStructure.getSenderSigningKey() 96 | .getPublic() 97 | .toByteArray(), 0); 98 | } 99 | 100 | public ECPrivateKey getSigningKeyPrivate() { 101 | return Curve.decodePrivatePoint(senderKeyStateStructure.getSenderSigningKey() 102 | .getPrivate().toByteArray()); 103 | } 104 | 105 | public boolean hasSenderMessageKey(int iteration) { 106 | for (SenderKeyStateStructure.SenderMessageKey senderMessageKey : senderKeyStateStructure.getSenderMessageKeysList()) { 107 | if (senderMessageKey.getIteration() == iteration) return true; 108 | } 109 | 110 | return false; 111 | } 112 | 113 | public void addSenderMessageKey(SenderMessageKey senderMessageKey) { 114 | SenderKeyStateStructure.SenderMessageKey senderMessageKeyStructure = 115 | SenderKeyStateStructure.SenderMessageKey.newBuilder() 116 | .setIteration(senderMessageKey.getIteration()) 117 | .setSeed(ByteString.copyFrom(senderMessageKey.getSeed())) 118 | .build(); 119 | 120 | SenderKeyStateStructure.Builder builder = this.senderKeyStateStructure.toBuilder(); 121 | 122 | builder.addSenderMessageKeys(senderMessageKeyStructure); 123 | 124 | if (builder.getSenderMessageKeysCount() > MAX_MESSAGE_KEYS) { 125 | builder.removeSenderMessageKeys(0); 126 | } 127 | 128 | this.senderKeyStateStructure = builder.build(); 129 | } 130 | 131 | public SenderMessageKey removeSenderMessageKey(int iteration) { 132 | List keys = new LinkedList<>(senderKeyStateStructure.getSenderMessageKeysList()); 133 | Iterator iterator = keys.iterator(); 134 | 135 | SenderKeyStateStructure.SenderMessageKey result = null; 136 | 137 | while (iterator.hasNext()) { 138 | SenderKeyStateStructure.SenderMessageKey senderMessageKey = iterator.next(); 139 | 140 | if (senderMessageKey.getIteration() == iteration) { 141 | result = senderMessageKey; 142 | iterator.remove(); 143 | break; 144 | } 145 | } 146 | 147 | this.senderKeyStateStructure = this.senderKeyStateStructure.toBuilder() 148 | .clearSenderMessageKeys() 149 | .addAllSenderMessageKeys(keys) 150 | .build(); 151 | 152 | if (result != null) { 153 | return new SenderMessageKey(result.getIteration(), result.getSeed().toByteArray()); 154 | } else { 155 | return null; 156 | } 157 | } 158 | 159 | public SenderKeyStateStructure getStructure() { 160 | return senderKeyStateStructure; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.groups.state; 7 | 8 | import org.whispersystems.libsignal.groups.SenderKeyName; 9 | 10 | public interface SenderKeyStore { 11 | 12 | /** 13 | * Commit to storage the {@link org.whispersystems.libsignal.groups.state.SenderKeyRecord} for a 14 | * given (groupId + senderId + deviceId) tuple. 15 | * 16 | * @param senderKeyName the (groupId + senderId + deviceId) tuple. 17 | * @param record the current SenderKeyRecord for the specified senderKeyName. 18 | */ 19 | public void storeSenderKey(SenderKeyName senderKeyName, SenderKeyRecord record); 20 | 21 | /** 22 | * Returns a copy of the {@link org.whispersystems.libsignal.groups.state.SenderKeyRecord} 23 | * corresponding to the (groupId + senderId + deviceId) tuple, or a new SenderKeyRecord if 24 | * one does not currently exist. 25 | *

26 | * It is important that implementations return a copy of the current durable information. The 27 | * returned SenderKeyRecord may be modified, but those changes should not have an effect on the 28 | * durable session state (what is returned by subsequent calls to this method) without the 29 | * store method being called here first. 30 | * 31 | * @param senderKeyName The (groupId + senderId + deviceId) tuple. 32 | * @return a copy of the SenderKeyRecord corresponding to the (groupId + senderId + deviceId tuple, or 33 | * a new SenderKeyRecord if one does not currently exist. 34 | */ 35 | 36 | public SenderKeyRecord loadSenderKey(SenderKeyName senderKeyName); 37 | } 38 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/DerivedMessageSecrets.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.kdf; 8 | 9 | import org.whispersystems.libsignal.util.ByteUtil; 10 | 11 | import java.text.ParseException; 12 | 13 | import javax.crypto.spec.IvParameterSpec; 14 | import javax.crypto.spec.SecretKeySpec; 15 | 16 | public class DerivedMessageSecrets { 17 | 18 | public static final int SIZE = 80; 19 | private static final int CIPHER_KEY_LENGTH = 32; 20 | private static final int MAC_KEY_LENGTH = 32; 21 | private static final int IV_LENGTH = 16; 22 | 23 | private final SecretKeySpec cipherKey; 24 | private final SecretKeySpec macKey; 25 | private final IvParameterSpec iv; 26 | 27 | public DerivedMessageSecrets(byte[] okm) { 28 | try { 29 | byte[][] keys = ByteUtil.split(okm, CIPHER_KEY_LENGTH, MAC_KEY_LENGTH, IV_LENGTH); 30 | 31 | this.cipherKey = new SecretKeySpec(keys[0], "AES"); 32 | this.macKey = new SecretKeySpec(keys[1], "HmacSHA256"); 33 | this.iv = new IvParameterSpec(keys[2]); 34 | } catch (ParseException e) { 35 | throw new AssertionError(e); 36 | } 37 | } 38 | 39 | public SecretKeySpec getCipherKey() { 40 | return cipherKey; 41 | } 42 | 43 | public SecretKeySpec getMacKey() { 44 | return macKey; 45 | } 46 | 47 | public IvParameterSpec getIv() { 48 | return iv; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/DerivedRootSecrets.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.kdf; 7 | 8 | import org.whispersystems.libsignal.util.ByteUtil; 9 | 10 | public class DerivedRootSecrets { 11 | 12 | public static final int SIZE = 64; 13 | 14 | private final byte[] rootKey; 15 | private final byte[] chainKey; 16 | 17 | public DerivedRootSecrets(byte[] okm) { 18 | byte[][] keys = ByteUtil.split(okm, 32, 32); 19 | this.rootKey = keys[0]; 20 | this.chainKey = keys[1]; 21 | } 22 | 23 | public byte[] getRootKey() { 24 | return rootKey; 25 | } 26 | 27 | public byte[] getChainKey() { 28 | return chainKey; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/kdf/HKDF.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | 7 | package org.whispersystems.libsignal.kdf; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.security.InvalidKeyException; 11 | import java.security.NoSuchAlgorithmException; 12 | 13 | import javax.crypto.Mac; 14 | import javax.crypto.spec.SecretKeySpec; 15 | 16 | public abstract class HKDF { 17 | 18 | private static final int HASH_OUTPUT_SIZE = 32; 19 | 20 | public static HKDF createFor(int messageVersion) { 21 | switch (messageVersion) { 22 | case 2: return new HKDFv2(); 23 | case 3: return new HKDFv3(); 24 | default: throw new AssertionError("Unknown version: " + messageVersion); 25 | } 26 | } 27 | 28 | public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] info, int outputLength) { 29 | byte[] salt = new byte[HASH_OUTPUT_SIZE]; 30 | return deriveSecrets(inputKeyMaterial, salt, info, outputLength); 31 | } 32 | 33 | public byte[] deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info, int outputLength) { 34 | byte[] prk = extract(salt, inputKeyMaterial); 35 | return expand(prk, info, outputLength); 36 | } 37 | 38 | private byte[] extract(byte[] salt, byte[] inputKeyMaterial) { 39 | try { 40 | Mac mac = Mac.getInstance("HmacSHA256"); 41 | mac.init(new SecretKeySpec(salt, "HmacSHA256")); 42 | return mac.doFinal(inputKeyMaterial); 43 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 44 | throw new AssertionError(e); 45 | } 46 | } 47 | 48 | private byte[] expand(byte[] prk, byte[] info, int outputSize) { 49 | try { 50 | int iterations = (int) Math.ceil((double) outputSize / (double) HASH_OUTPUT_SIZE); 51 | byte[] mixin = new byte[0]; 52 | ByteArrayOutputStream results = new ByteArrayOutputStream(); 53 | int remainingBytes = outputSize; 54 | 55 | for (int i= getIterationStartOffset();i preKeyId; 27 | private final int signedPreKeyId; 28 | private final ECPublicKey baseKey; 29 | private final IdentityKey identityKey; 30 | private final SignalMessage message; 31 | private final byte[] serialized; 32 | 33 | public PreKeySignalMessage(byte[] serialized) 34 | throws InvalidMessageException, InvalidVersionException 35 | { 36 | try { 37 | this.version = ByteUtil.highBitsToInt(serialized[0]); 38 | 39 | if (this.version > CiphertextMessage.CURRENT_VERSION) { 40 | throw new InvalidVersionException("Unknown version: " + this.version); 41 | } 42 | 43 | if (this.version < CiphertextMessage.CURRENT_VERSION) { 44 | throw new LegacyMessageException("Legacy version: " + this.version); 45 | } 46 | 47 | SignalProtos.PreKeySignalMessage preKeyWhisperMessage 48 | = SignalProtos.PreKeySignalMessage.parseFrom(ByteString.copyFrom(serialized, 1, 49 | serialized.length-1)); 50 | 51 | if (!preKeyWhisperMessage.hasSignedPreKeyId() || 52 | !preKeyWhisperMessage.hasBaseKey() || 53 | !preKeyWhisperMessage.hasIdentityKey() || 54 | !preKeyWhisperMessage.hasMessage()) 55 | { 56 | throw new InvalidMessageException("Incomplete message."); 57 | } 58 | 59 | this.serialized = serialized; 60 | this.registrationId = preKeyWhisperMessage.getRegistrationId(); 61 | this.preKeyId = preKeyWhisperMessage.hasPreKeyId() ? Optional.of(preKeyWhisperMessage.getPreKeyId()) : Optional.absent(); 62 | this.signedPreKeyId = preKeyWhisperMessage.hasSignedPreKeyId() ? preKeyWhisperMessage.getSignedPreKeyId() : -1; 63 | this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0); 64 | this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0)); 65 | this.message = new SignalMessage(preKeyWhisperMessage.getMessage().toByteArray()); 66 | } catch (InvalidProtocolBufferException | InvalidKeyException | LegacyMessageException e) { 67 | throw new InvalidMessageException(e); 68 | } 69 | } 70 | 71 | public PreKeySignalMessage(int messageVersion, int registrationId, Optional preKeyId, 72 | int signedPreKeyId, ECPublicKey baseKey, IdentityKey identityKey, 73 | SignalMessage message) 74 | { 75 | this.version = messageVersion; 76 | this.registrationId = registrationId; 77 | this.preKeyId = preKeyId; 78 | this.signedPreKeyId = signedPreKeyId; 79 | this.baseKey = baseKey; 80 | this.identityKey = identityKey; 81 | this.message = message; 82 | 83 | SignalProtos.PreKeySignalMessage.Builder builder = 84 | SignalProtos.PreKeySignalMessage.newBuilder() 85 | .setSignedPreKeyId(signedPreKeyId) 86 | .setBaseKey(ByteString.copyFrom(baseKey.serialize())) 87 | .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) 88 | .setMessage(ByteString.copyFrom(message.serialize())) 89 | .setRegistrationId(registrationId); 90 | 91 | if (preKeyId.isPresent()) { 92 | builder.setPreKeyId(preKeyId.get()); 93 | } 94 | 95 | byte[] versionBytes = {ByteUtil.intsToByteHighAndLow(this.version, CURRENT_VERSION)}; 96 | byte[] messageBytes = builder.build().toByteArray(); 97 | 98 | this.serialized = ByteUtil.combine(versionBytes, messageBytes); 99 | } 100 | 101 | public int getMessageVersion() { 102 | return version; 103 | } 104 | 105 | public IdentityKey getIdentityKey() { 106 | return identityKey; 107 | } 108 | 109 | public int getRegistrationId() { 110 | return registrationId; 111 | } 112 | 113 | public Optional getPreKeyId() { 114 | return preKeyId; 115 | } 116 | 117 | public int getSignedPreKeyId() { 118 | return signedPreKeyId; 119 | } 120 | 121 | public ECPublicKey getBaseKey() { 122 | return baseKey; 123 | } 124 | 125 | public SignalMessage getWhisperMessage() { 126 | return message; 127 | } 128 | 129 | @Override 130 | public byte[] serialize() { 131 | return serialized; 132 | } 133 | 134 | @Override 135 | public int getType() { 136 | return CiphertextMessage.PREKEY_TYPE; 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/SenderKeyDistributionMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.protocol; 7 | 8 | import com.google.protobuf.ByteString; 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | 11 | import org.whispersystems.libsignal.InvalidKeyException; 12 | import org.whispersystems.libsignal.InvalidMessageException; 13 | import org.whispersystems.libsignal.LegacyMessageException; 14 | import org.whispersystems.libsignal.ecc.Curve; 15 | import org.whispersystems.libsignal.ecc.ECPublicKey; 16 | import org.whispersystems.libsignal.util.ByteUtil; 17 | 18 | public class SenderKeyDistributionMessage implements CiphertextMessage { 19 | 20 | private final int id; 21 | private final int iteration; 22 | private final byte[] chainKey; 23 | private final ECPublicKey signatureKey; 24 | private final byte[] serialized; 25 | 26 | public SenderKeyDistributionMessage(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { 27 | byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)}; 28 | byte[] protobuf = SignalProtos.SenderKeyDistributionMessage.newBuilder() 29 | .setId(id) 30 | .setIteration(iteration) 31 | .setChainKey(ByteString.copyFrom(chainKey)) 32 | .setSigningKey(ByteString.copyFrom(signatureKey.serialize())) 33 | .build().toByteArray(); 34 | 35 | this.id = id; 36 | this.iteration = iteration; 37 | this.chainKey = chainKey; 38 | this.signatureKey = signatureKey; 39 | this.serialized = ByteUtil.combine(version, protobuf); 40 | } 41 | 42 | public SenderKeyDistributionMessage(byte[] serialized) throws LegacyMessageException, InvalidMessageException { 43 | try { 44 | byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1); 45 | byte version = messageParts[0][0]; 46 | byte[] message = messageParts[1]; 47 | 48 | if (ByteUtil.highBitsToInt(version) < CiphertextMessage.CURRENT_VERSION) { 49 | throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); 50 | } 51 | 52 | if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { 53 | throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); 54 | } 55 | 56 | SignalProtos.SenderKeyDistributionMessage distributionMessage = SignalProtos.SenderKeyDistributionMessage.parseFrom(message); 57 | 58 | if (!distributionMessage.hasId() || 59 | !distributionMessage.hasIteration() || 60 | !distributionMessage.hasChainKey() || 61 | !distributionMessage.hasSigningKey()) 62 | { 63 | throw new InvalidMessageException("Incomplete message."); 64 | } 65 | 66 | this.serialized = serialized; 67 | this.id = distributionMessage.getId(); 68 | this.iteration = distributionMessage.getIteration(); 69 | this.chainKey = distributionMessage.getChainKey().toByteArray(); 70 | this.signatureKey = Curve.decodePoint(distributionMessage.getSigningKey().toByteArray(), 0); 71 | } catch (InvalidProtocolBufferException | InvalidKeyException e) { 72 | throw new InvalidMessageException(e); 73 | } 74 | } 75 | 76 | @Override 77 | public byte[] serialize() { 78 | return serialized; 79 | } 80 | 81 | @Override 82 | public int getType() { 83 | return SENDERKEY_DISTRIBUTION_TYPE; 84 | } 85 | 86 | public int getIteration() { 87 | return iteration; 88 | } 89 | 90 | public byte[] getChainKey() { 91 | return chainKey; 92 | } 93 | 94 | public ECPublicKey getSignatureKey() { 95 | return signatureKey; 96 | } 97 | 98 | public int getId() { 99 | return id; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/SenderKeyMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.protocol; 7 | 8 | import com.google.protobuf.ByteString; 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | 11 | import org.whispersystems.libsignal.InvalidKeyException; 12 | import org.whispersystems.libsignal.InvalidMessageException; 13 | import org.whispersystems.libsignal.LegacyMessageException; 14 | import org.whispersystems.libsignal.ecc.Curve; 15 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 16 | import org.whispersystems.libsignal.ecc.ECPublicKey; 17 | import org.whispersystems.libsignal.util.ByteUtil; 18 | 19 | import java.text.ParseException; 20 | 21 | public class SenderKeyMessage implements CiphertextMessage { 22 | 23 | private static final int SIGNATURE_LENGTH = 64; 24 | 25 | private final int messageVersion; 26 | private final int keyId; 27 | private final int iteration; 28 | private final byte[] ciphertext; 29 | private final byte[] serialized; 30 | 31 | public SenderKeyMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException { 32 | try { 33 | byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - SIGNATURE_LENGTH, SIGNATURE_LENGTH); 34 | byte version = messageParts[0][0]; 35 | byte[] message = messageParts[1]; 36 | byte[] signature = messageParts[2]; 37 | 38 | if (ByteUtil.highBitsToInt(version) < 3) { 39 | throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); 40 | } 41 | 42 | if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { 43 | throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); 44 | } 45 | 46 | SignalProtos.SenderKeyMessage senderKeyMessage = SignalProtos.SenderKeyMessage.parseFrom(message); 47 | 48 | if (!senderKeyMessage.hasId() || 49 | !senderKeyMessage.hasIteration() || 50 | !senderKeyMessage.hasCiphertext()) 51 | { 52 | throw new InvalidMessageException("Incomplete message."); 53 | } 54 | 55 | this.serialized = serialized; 56 | this.messageVersion = ByteUtil.highBitsToInt(version); 57 | this.keyId = senderKeyMessage.getId(); 58 | this.iteration = senderKeyMessage.getIteration(); 59 | this.ciphertext = senderKeyMessage.getCiphertext().toByteArray(); 60 | } catch (InvalidProtocolBufferException | ParseException e) { 61 | throw new InvalidMessageException(e); 62 | } 63 | } 64 | 65 | public SenderKeyMessage(int keyId, int iteration, byte[] ciphertext, ECPrivateKey signatureKey) { 66 | byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)}; 67 | byte[] message = SignalProtos.SenderKeyMessage.newBuilder() 68 | .setId(keyId) 69 | .setIteration(iteration) 70 | .setCiphertext(ByteString.copyFrom(ciphertext)) 71 | .build().toByteArray(); 72 | 73 | byte[] signature = getSignature(signatureKey, ByteUtil.combine(version, message)); 74 | 75 | this.serialized = ByteUtil.combine(version, message, signature); 76 | this.messageVersion = CURRENT_VERSION; 77 | this.keyId = keyId; 78 | this.iteration = iteration; 79 | this.ciphertext = ciphertext; 80 | } 81 | 82 | public int getKeyId() { 83 | return keyId; 84 | } 85 | 86 | public int getIteration() { 87 | return iteration; 88 | } 89 | 90 | public byte[] getCipherText() { 91 | return ciphertext; 92 | } 93 | 94 | public void verifySignature(ECPublicKey signatureKey) 95 | throws InvalidMessageException 96 | { 97 | try { 98 | byte[][] parts = ByteUtil.split(serialized, serialized.length - SIGNATURE_LENGTH, SIGNATURE_LENGTH); 99 | 100 | if (!Curve.verifySignature(signatureKey, parts[0], parts[1])) { 101 | throw new InvalidMessageException("Invalid signature!"); 102 | } 103 | 104 | } catch (InvalidKeyException e) { 105 | throw new InvalidMessageException(e); 106 | } 107 | } 108 | 109 | private byte[] getSignature(ECPrivateKey signatureKey, byte[] serialized) { 110 | try { 111 | return Curve.calculateSignature(signatureKey, serialized); 112 | } catch (InvalidKeyException e) { 113 | throw new AssertionError(e); 114 | } 115 | } 116 | 117 | @Override 118 | public byte[] serialize() { 119 | return serialized; 120 | } 121 | 122 | @Override 123 | public int getType() { 124 | return CiphertextMessage.SENDERKEY_TYPE; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/protocol/SignalMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.protocol; 7 | 8 | import com.google.protobuf.ByteString; 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | 11 | import org.whispersystems.libsignal.IdentityKey; 12 | import org.whispersystems.libsignal.InvalidKeyException; 13 | import org.whispersystems.libsignal.InvalidMessageException; 14 | import org.whispersystems.libsignal.LegacyMessageException; 15 | import org.whispersystems.libsignal.ecc.Curve; 16 | import org.whispersystems.libsignal.ecc.ECPublicKey; 17 | import org.whispersystems.libsignal.util.ByteUtil; 18 | 19 | import java.security.MessageDigest; 20 | import java.security.NoSuchAlgorithmException; 21 | import java.text.ParseException; 22 | 23 | import javax.crypto.Mac; 24 | import javax.crypto.spec.SecretKeySpec; 25 | 26 | public class SignalMessage implements CiphertextMessage { 27 | 28 | private static final int MAC_LENGTH = 8; 29 | 30 | private final int messageVersion; 31 | private final ECPublicKey senderRatchetKey; 32 | private final int counter; 33 | private final int previousCounter; 34 | private final byte[] ciphertext; 35 | private final byte[] serialized; 36 | 37 | public SignalMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException { 38 | try { 39 | byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH); 40 | byte version = messageParts[0][0]; 41 | byte[] message = messageParts[1]; 42 | byte[] mac = messageParts[2]; 43 | 44 | if (ByteUtil.highBitsToInt(version) < CURRENT_VERSION) { 45 | throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); 46 | } 47 | 48 | if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { 49 | throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); 50 | } 51 | 52 | SignalProtos.SignalMessage whisperMessage = SignalProtos.SignalMessage.parseFrom(message); 53 | 54 | if (!whisperMessage.hasCiphertext() || 55 | !whisperMessage.hasCounter() || 56 | !whisperMessage.hasRatchetKey()) 57 | { 58 | throw new InvalidMessageException("Incomplete message."); 59 | } 60 | 61 | this.serialized = serialized; 62 | this.senderRatchetKey = Curve.decodePoint(whisperMessage.getRatchetKey().toByteArray(), 0); 63 | this.messageVersion = ByteUtil.highBitsToInt(version); 64 | this.counter = whisperMessage.getCounter(); 65 | this.previousCounter = whisperMessage.getPreviousCounter(); 66 | this.ciphertext = whisperMessage.getCiphertext().toByteArray(); 67 | } catch (InvalidProtocolBufferException | InvalidKeyException | ParseException e) { 68 | throw new InvalidMessageException(e); 69 | } 70 | } 71 | 72 | public SignalMessage(int messageVersion, SecretKeySpec macKey, ECPublicKey senderRatchetKey, 73 | int counter, int previousCounter, byte[] ciphertext, 74 | IdentityKey senderIdentityKey, 75 | IdentityKey receiverIdentityKey) 76 | { 77 | byte[] version = {ByteUtil.intsToByteHighAndLow(messageVersion, CURRENT_VERSION)}; 78 | byte[] message = SignalProtos.SignalMessage.newBuilder() 79 | .setRatchetKey(ByteString.copyFrom(senderRatchetKey.serialize())) 80 | .setCounter(counter) 81 | .setPreviousCounter(previousCounter) 82 | .setCiphertext(ByteString.copyFrom(ciphertext)) 83 | .build().toByteArray(); 84 | 85 | byte[] mac = getMac(senderIdentityKey, receiverIdentityKey, macKey, ByteUtil.combine(version, message)); 86 | 87 | this.serialized = ByteUtil.combine(version, message, mac); 88 | this.senderRatchetKey = senderRatchetKey; 89 | this.counter = counter; 90 | this.previousCounter = previousCounter; 91 | this.ciphertext = ciphertext; 92 | this.messageVersion = messageVersion; 93 | } 94 | 95 | public ECPublicKey getSenderRatchetKey() { 96 | return senderRatchetKey; 97 | } 98 | 99 | public int getMessageVersion() { 100 | return messageVersion; 101 | } 102 | 103 | public int getCounter() { 104 | return counter; 105 | } 106 | 107 | public byte[] getBody() { 108 | return ciphertext; 109 | } 110 | 111 | public void verifyMac(IdentityKey senderIdentityKey, IdentityKey receiverIdentityKey, SecretKeySpec macKey) 112 | throws InvalidMessageException 113 | { 114 | byte[][] parts = ByteUtil.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH); 115 | byte[] ourMac = getMac(senderIdentityKey, receiverIdentityKey, macKey, parts[0]); 116 | byte[] theirMac = parts[1]; 117 | 118 | if (!MessageDigest.isEqual(ourMac, theirMac)) { 119 | throw new InvalidMessageException("Bad Mac!"); 120 | } 121 | } 122 | 123 | private byte[] getMac(IdentityKey senderIdentityKey, 124 | IdentityKey receiverIdentityKey, 125 | SecretKeySpec macKey, byte[] serialized) 126 | { 127 | try { 128 | Mac mac = Mac.getInstance("HmacSHA256"); 129 | mac.init(macKey); 130 | 131 | mac.update(senderIdentityKey.getPublicKey().serialize()); 132 | mac.update(receiverIdentityKey.getPublicKey().serialize()); 133 | 134 | byte[] fullMac = mac.doFinal(serialized); 135 | return ByteUtil.trim(fullMac, MAC_LENGTH); 136 | } catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) { 137 | throw new AssertionError(e); 138 | } 139 | } 140 | 141 | @Override 142 | public byte[] serialize() { 143 | return serialized; 144 | } 145 | 146 | @Override 147 | public int getType() { 148 | return CiphertextMessage.WHISPER_TYPE; 149 | } 150 | 151 | public static boolean isLegacy(byte[] message) { 152 | return message != null && message.length >= 1 && 153 | ByteUtil.highBitsToInt(message[0]) != CiphertextMessage.CURRENT_VERSION; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/AliceSignalProtocolParameters.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ratchet; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.IdentityKeyPair; 10 | import org.whispersystems.libsignal.ecc.ECKeyPair; 11 | import org.whispersystems.libsignal.ecc.ECPublicKey; 12 | import org.whispersystems.libsignal.util.guava.Optional; 13 | 14 | public class AliceSignalProtocolParameters { 15 | 16 | private final IdentityKeyPair ourIdentityKey; 17 | private final ECKeyPair ourBaseKey; 18 | 19 | private final IdentityKey theirIdentityKey; 20 | private final ECPublicKey theirSignedPreKey; 21 | private final Optional theirOneTimePreKey; 22 | private final ECPublicKey theirRatchetKey; 23 | 24 | private AliceSignalProtocolParameters(IdentityKeyPair ourIdentityKey, ECKeyPair ourBaseKey, 25 | IdentityKey theirIdentityKey, ECPublicKey theirSignedPreKey, 26 | ECPublicKey theirRatchetKey, Optional theirOneTimePreKey) 27 | { 28 | this.ourIdentityKey = ourIdentityKey; 29 | this.ourBaseKey = ourBaseKey; 30 | this.theirIdentityKey = theirIdentityKey; 31 | this.theirSignedPreKey = theirSignedPreKey; 32 | this.theirRatchetKey = theirRatchetKey; 33 | this.theirOneTimePreKey = theirOneTimePreKey; 34 | 35 | if (ourIdentityKey == null || ourBaseKey == null || theirIdentityKey == null || 36 | theirSignedPreKey == null || theirRatchetKey == null || theirOneTimePreKey == null) 37 | { 38 | throw new IllegalArgumentException("Null values!"); 39 | } 40 | } 41 | 42 | public IdentityKeyPair getOurIdentityKey() { 43 | return ourIdentityKey; 44 | } 45 | 46 | public ECKeyPair getOurBaseKey() { 47 | return ourBaseKey; 48 | } 49 | 50 | public IdentityKey getTheirIdentityKey() { 51 | return theirIdentityKey; 52 | } 53 | 54 | public ECPublicKey getTheirSignedPreKey() { 55 | return theirSignedPreKey; 56 | } 57 | 58 | public Optional getTheirOneTimePreKey() { 59 | return theirOneTimePreKey; 60 | } 61 | 62 | public static Builder newBuilder() { 63 | return new Builder(); 64 | } 65 | 66 | public ECPublicKey getTheirRatchetKey() { 67 | return theirRatchetKey; 68 | } 69 | 70 | public static class Builder { 71 | private IdentityKeyPair ourIdentityKey; 72 | private ECKeyPair ourBaseKey; 73 | 74 | private IdentityKey theirIdentityKey; 75 | private ECPublicKey theirSignedPreKey; 76 | private ECPublicKey theirRatchetKey; 77 | private Optional theirOneTimePreKey; 78 | 79 | public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) { 80 | this.ourIdentityKey = ourIdentityKey; 81 | return this; 82 | } 83 | 84 | public Builder setOurBaseKey(ECKeyPair ourBaseKey) { 85 | this.ourBaseKey = ourBaseKey; 86 | return this; 87 | } 88 | 89 | public Builder setTheirRatchetKey(ECPublicKey theirRatchetKey) { 90 | this.theirRatchetKey = theirRatchetKey; 91 | return this; 92 | } 93 | 94 | public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) { 95 | this.theirIdentityKey = theirIdentityKey; 96 | return this; 97 | } 98 | 99 | public Builder setTheirSignedPreKey(ECPublicKey theirSignedPreKey) { 100 | this.theirSignedPreKey = theirSignedPreKey; 101 | return this; 102 | } 103 | 104 | public Builder setTheirOneTimePreKey(Optional theirOneTimePreKey) { 105 | this.theirOneTimePreKey = theirOneTimePreKey; 106 | return this; 107 | } 108 | 109 | public AliceSignalProtocolParameters create() { 110 | return new AliceSignalProtocolParameters(ourIdentityKey, ourBaseKey, theirIdentityKey, 111 | theirSignedPreKey, theirRatchetKey, theirOneTimePreKey); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/BobSignalProtocolParameters.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ratchet; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.IdentityKeyPair; 10 | import org.whispersystems.libsignal.ecc.ECKeyPair; 11 | import org.whispersystems.libsignal.ecc.ECPublicKey; 12 | import org.whispersystems.libsignal.util.guava.Optional; 13 | 14 | public class BobSignalProtocolParameters { 15 | 16 | private final IdentityKeyPair ourIdentityKey; 17 | private final ECKeyPair ourSignedPreKey; 18 | private final Optional ourOneTimePreKey; 19 | private final ECKeyPair ourRatchetKey; 20 | 21 | private final IdentityKey theirIdentityKey; 22 | private final ECPublicKey theirBaseKey; 23 | 24 | BobSignalProtocolParameters(IdentityKeyPair ourIdentityKey, ECKeyPair ourSignedPreKey, 25 | ECKeyPair ourRatchetKey, Optional ourOneTimePreKey, 26 | IdentityKey theirIdentityKey, ECPublicKey theirBaseKey) 27 | { 28 | this.ourIdentityKey = ourIdentityKey; 29 | this.ourSignedPreKey = ourSignedPreKey; 30 | this.ourRatchetKey = ourRatchetKey; 31 | this.ourOneTimePreKey = ourOneTimePreKey; 32 | this.theirIdentityKey = theirIdentityKey; 33 | this.theirBaseKey = theirBaseKey; 34 | 35 | if (ourIdentityKey == null || ourSignedPreKey == null || ourRatchetKey == null || 36 | ourOneTimePreKey == null || theirIdentityKey == null || theirBaseKey == null) 37 | { 38 | throw new IllegalArgumentException("Null value!"); 39 | } 40 | } 41 | 42 | public IdentityKeyPair getOurIdentityKey() { 43 | return ourIdentityKey; 44 | } 45 | 46 | public ECKeyPair getOurSignedPreKey() { 47 | return ourSignedPreKey; 48 | } 49 | 50 | public Optional getOurOneTimePreKey() { 51 | return ourOneTimePreKey; 52 | } 53 | 54 | public IdentityKey getTheirIdentityKey() { 55 | return theirIdentityKey; 56 | } 57 | 58 | public ECPublicKey getTheirBaseKey() { 59 | return theirBaseKey; 60 | } 61 | 62 | public static Builder newBuilder() { 63 | return new Builder(); 64 | } 65 | 66 | public ECKeyPair getOurRatchetKey() { 67 | return ourRatchetKey; 68 | } 69 | 70 | public static class Builder { 71 | private IdentityKeyPair ourIdentityKey; 72 | private ECKeyPair ourSignedPreKey; 73 | private Optional ourOneTimePreKey; 74 | private ECKeyPair ourRatchetKey; 75 | 76 | private IdentityKey theirIdentityKey; 77 | private ECPublicKey theirBaseKey; 78 | 79 | public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) { 80 | this.ourIdentityKey = ourIdentityKey; 81 | return this; 82 | } 83 | 84 | public Builder setOurSignedPreKey(ECKeyPair ourSignedPreKey) { 85 | this.ourSignedPreKey = ourSignedPreKey; 86 | return this; 87 | } 88 | 89 | public Builder setOurOneTimePreKey(Optional ourOneTimePreKey) { 90 | this.ourOneTimePreKey = ourOneTimePreKey; 91 | return this; 92 | } 93 | 94 | public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) { 95 | this.theirIdentityKey = theirIdentityKey; 96 | return this; 97 | } 98 | 99 | public Builder setTheirBaseKey(ECPublicKey theirBaseKey) { 100 | this.theirBaseKey = theirBaseKey; 101 | return this; 102 | } 103 | 104 | public Builder setOurRatchetKey(ECKeyPair ourRatchetKey) { 105 | this.ourRatchetKey = ourRatchetKey; 106 | return this; 107 | } 108 | 109 | public BobSignalProtocolParameters create() { 110 | return new BobSignalProtocolParameters(ourIdentityKey, ourSignedPreKey, ourRatchetKey, 111 | ourOneTimePreKey, theirIdentityKey, theirBaseKey); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/ChainKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ratchet; 7 | 8 | 9 | import org.whispersystems.libsignal.kdf.DerivedMessageSecrets; 10 | import org.whispersystems.libsignal.kdf.HKDF; 11 | 12 | import java.security.InvalidKeyException; 13 | import java.security.NoSuchAlgorithmException; 14 | 15 | import javax.crypto.Mac; 16 | import javax.crypto.spec.SecretKeySpec; 17 | 18 | public class ChainKey { 19 | 20 | private static final byte[] MESSAGE_KEY_SEED = {0x01}; 21 | private static final byte[] CHAIN_KEY_SEED = {0x02}; 22 | 23 | private final HKDF kdf; 24 | private final byte[] key; 25 | private final int index; 26 | 27 | public ChainKey(HKDF kdf, byte[] key, int index) { 28 | this.kdf = kdf; 29 | this.key = key; 30 | this.index = index; 31 | } 32 | 33 | public byte[] getKey() { 34 | return key; 35 | } 36 | 37 | public int getIndex() { 38 | return index; 39 | } 40 | 41 | public ChainKey getNextChainKey() { 42 | byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED); 43 | return new ChainKey(kdf, nextKey, index + 1); 44 | } 45 | 46 | public MessageKeys getMessageKeys() { 47 | byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED); 48 | byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE); 49 | DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes); 50 | 51 | return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), keyMaterial.getIv(), index); 52 | } 53 | 54 | private byte[] getBaseMaterial(byte[] seed) { 55 | try { 56 | Mac mac = Mac.getInstance("HmacSHA256"); 57 | mac.init(new SecretKeySpec(key, "HmacSHA256")); 58 | 59 | return mac.doFinal(seed); 60 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { 61 | throw new AssertionError(e); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/MessageKeys.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ratchet; 7 | 8 | import javax.crypto.spec.IvParameterSpec; 9 | import javax.crypto.spec.SecretKeySpec; 10 | 11 | public class MessageKeys { 12 | 13 | private final SecretKeySpec cipherKey; 14 | private final SecretKeySpec macKey; 15 | private final IvParameterSpec iv; 16 | private final int counter; 17 | 18 | public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, IvParameterSpec iv, int counter) { 19 | this.cipherKey = cipherKey; 20 | this.macKey = macKey; 21 | this.iv = iv; 22 | this.counter = counter; 23 | } 24 | 25 | public SecretKeySpec getCipherKey() { 26 | return cipherKey; 27 | } 28 | 29 | public SecretKeySpec getMacKey() { 30 | return macKey; 31 | } 32 | 33 | public IvParameterSpec getIv() { 34 | return iv; 35 | } 36 | 37 | public int getCounter() { 38 | return counter; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/RootKey.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ratchet; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyException; 9 | import org.whispersystems.libsignal.ecc.Curve; 10 | import org.whispersystems.libsignal.ecc.ECKeyPair; 11 | import org.whispersystems.libsignal.ecc.ECPublicKey; 12 | import org.whispersystems.libsignal.kdf.DerivedRootSecrets; 13 | import org.whispersystems.libsignal.kdf.HKDF; 14 | import org.whispersystems.libsignal.util.ByteUtil; 15 | import org.whispersystems.libsignal.util.Pair; 16 | 17 | public class RootKey { 18 | 19 | private final HKDF kdf; 20 | private final byte[] key; 21 | 22 | public RootKey(HKDF kdf, byte[] key) { 23 | this.kdf = kdf; 24 | this.key = key; 25 | } 26 | 27 | public byte[] getKeyBytes() { 28 | return key; 29 | } 30 | 31 | public Pair createChain(ECPublicKey theirRatchetKey, ECKeyPair ourRatchetKey) 32 | throws InvalidKeyException 33 | { 34 | byte[] sharedSecret = Curve.calculateAgreement(theirRatchetKey, ourRatchetKey.getPrivateKey()); 35 | byte[] derivedSecretBytes = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes(), DerivedRootSecrets.SIZE); 36 | DerivedRootSecrets derivedSecrets = new DerivedRootSecrets(derivedSecretBytes); 37 | 38 | RootKey newRootKey = new RootKey(kdf, derivedSecrets.getRootKey()); 39 | ChainKey newChainKey = new ChainKey(kdf, derivedSecrets.getChainKey(), 0); 40 | 41 | return new Pair<>(newRootKey, newChainKey); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/ratchet/SymmetricSignalProtocolParameters.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.ratchet; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.IdentityKeyPair; 10 | import org.whispersystems.libsignal.ecc.ECKeyPair; 11 | import org.whispersystems.libsignal.ecc.ECPublicKey; 12 | 13 | public class SymmetricSignalProtocolParameters { 14 | 15 | private final ECKeyPair ourBaseKey; 16 | private final ECKeyPair ourRatchetKey; 17 | private final IdentityKeyPair ourIdentityKey; 18 | 19 | private final ECPublicKey theirBaseKey; 20 | private final ECPublicKey theirRatchetKey; 21 | private final IdentityKey theirIdentityKey; 22 | 23 | SymmetricSignalProtocolParameters(ECKeyPair ourBaseKey, ECKeyPair ourRatchetKey, 24 | IdentityKeyPair ourIdentityKey, ECPublicKey theirBaseKey, 25 | ECPublicKey theirRatchetKey, IdentityKey theirIdentityKey) 26 | { 27 | this.ourBaseKey = ourBaseKey; 28 | this.ourRatchetKey = ourRatchetKey; 29 | this.ourIdentityKey = ourIdentityKey; 30 | this.theirBaseKey = theirBaseKey; 31 | this.theirRatchetKey = theirRatchetKey; 32 | this.theirIdentityKey = theirIdentityKey; 33 | 34 | if (ourBaseKey == null || ourRatchetKey == null || ourIdentityKey == null || 35 | theirBaseKey == null || theirRatchetKey == null || theirIdentityKey == null) 36 | { 37 | throw new IllegalArgumentException("Null values!"); 38 | } 39 | } 40 | 41 | public ECKeyPair getOurBaseKey() { 42 | return ourBaseKey; 43 | } 44 | 45 | public ECKeyPair getOurRatchetKey() { 46 | return ourRatchetKey; 47 | } 48 | 49 | public IdentityKeyPair getOurIdentityKey() { 50 | return ourIdentityKey; 51 | } 52 | 53 | public ECPublicKey getTheirBaseKey() { 54 | return theirBaseKey; 55 | } 56 | 57 | public ECPublicKey getTheirRatchetKey() { 58 | return theirRatchetKey; 59 | } 60 | 61 | public IdentityKey getTheirIdentityKey() { 62 | return theirIdentityKey; 63 | } 64 | 65 | public static Builder newBuilder() { 66 | return new Builder(); 67 | } 68 | 69 | public static class Builder { 70 | private ECKeyPair ourBaseKey; 71 | private ECKeyPair ourRatchetKey; 72 | private IdentityKeyPair ourIdentityKey; 73 | 74 | private ECPublicKey theirBaseKey; 75 | private ECPublicKey theirRatchetKey; 76 | private IdentityKey theirIdentityKey; 77 | 78 | public Builder setOurBaseKey(ECKeyPair ourBaseKey) { 79 | this.ourBaseKey = ourBaseKey; 80 | return this; 81 | } 82 | 83 | public Builder setOurRatchetKey(ECKeyPair ourRatchetKey) { 84 | this.ourRatchetKey = ourRatchetKey; 85 | return this; 86 | } 87 | 88 | public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) { 89 | this.ourIdentityKey = ourIdentityKey; 90 | return this; 91 | } 92 | 93 | public Builder setTheirBaseKey(ECPublicKey theirBaseKey) { 94 | this.theirBaseKey = theirBaseKey; 95 | return this; 96 | } 97 | 98 | public Builder setTheirRatchetKey(ECPublicKey theirRatchetKey) { 99 | this.theirRatchetKey = theirRatchetKey; 100 | return this; 101 | } 102 | 103 | public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) { 104 | this.theirIdentityKey = theirIdentityKey; 105 | return this; 106 | } 107 | 108 | public SymmetricSignalProtocolParameters create() { 109 | return new SymmetricSignalProtocolParameters(ourBaseKey, ourRatchetKey, ourIdentityKey, 110 | theirBaseKey, theirRatchetKey, theirIdentityKey); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/IdentityKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.IdentityKeyPair; 10 | import org.whispersystems.libsignal.SignalProtocolAddress; 11 | 12 | /** 13 | * Provides an interface to identity information. 14 | * 15 | * @author Moxie Marlinspike 16 | */ 17 | public interface IdentityKeyStore { 18 | 19 | public enum Direction { 20 | SENDING, RECEIVING 21 | } 22 | 23 | /** 24 | * Get the local client's identity key pair. 25 | * 26 | * @return The local client's persistent identity key pair. 27 | */ 28 | public IdentityKeyPair getIdentityKeyPair(); 29 | 30 | /** 31 | * Return the local client's registration ID. 32 | *

33 | * Clients should maintain a registration ID, a random number 34 | * between 1 and 16380 that's generated once at install time. 35 | * 36 | * @return the local client's registration ID. 37 | */ 38 | public int getLocalRegistrationId(); 39 | 40 | /** 41 | * Save a remote client's identity key 42 | *

43 | * Store a remote client's identity key as trusted. 44 | * 45 | * @param address The address of the remote client. 46 | * @param identityKey The remote client's identity key. 47 | * @return True if the identity key replaces a previous identity, false if not 48 | */ 49 | public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey); 50 | 51 | 52 | /** 53 | * Verify a remote client's identity key. 54 | *

55 | * Determine whether a remote client's identity is trusted. Convention is 56 | * that the Signal Protocol is 'trust on first use.' This means that 57 | * an identity key is considered 'trusted' if there is no entry for the recipient 58 | * in the local store, or if it matches the saved key for a recipient in the local 59 | * store. Only if it mismatches an entry in the local store is it considered 60 | * 'untrusted.' 61 | * 62 | * Clients may wish to make a distinction as to how keys are trusted based on the 63 | * direction of travel. For instance, clients may wish to accept all 'incoming' identity 64 | * key changes, while only blocking identity key changes when sending a message. 65 | * 66 | * @param address The address of the remote client. 67 | * @param identityKey The identity key to verify. 68 | * @param direction The direction (sending or receiving) this identity is being used for. 69 | * @return true if trusted, false if untrusted. 70 | */ 71 | public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction); 72 | 73 | 74 | /** 75 | * Return the saved public identity key for a remote client 76 | * 77 | * @param address The address of the remote client 78 | * @return The public identity key, or null if absent 79 | */ 80 | public IdentityKey getIdentity(SignalProtocolAddress address); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/PreKeyBundle.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.ecc.ECPublicKey; 10 | 11 | /** 12 | * A class that contains a remote PreKey and collection 13 | * of associated items. 14 | * 15 | * @author Moxie Marlinspike 16 | */ 17 | public class PreKeyBundle { 18 | 19 | private int registrationId; 20 | 21 | private int deviceId; 22 | 23 | private int preKeyId; 24 | private ECPublicKey preKeyPublic; 25 | 26 | private int signedPreKeyId; 27 | private ECPublicKey signedPreKeyPublic; 28 | private byte[] signedPreKeySignature; 29 | 30 | private IdentityKey identityKey; 31 | 32 | public PreKeyBundle(int registrationId, int deviceId, int preKeyId, ECPublicKey preKeyPublic, 33 | int signedPreKeyId, ECPublicKey signedPreKeyPublic, byte[] signedPreKeySignature, 34 | IdentityKey identityKey) 35 | { 36 | this.registrationId = registrationId; 37 | this.deviceId = deviceId; 38 | this.preKeyId = preKeyId; 39 | this.preKeyPublic = preKeyPublic; 40 | this.signedPreKeyId = signedPreKeyId; 41 | this.signedPreKeyPublic = signedPreKeyPublic; 42 | this.signedPreKeySignature = signedPreKeySignature; 43 | this.identityKey = identityKey; 44 | } 45 | 46 | /** 47 | * @return the device ID this PreKey belongs to. 48 | */ 49 | public int getDeviceId() { 50 | return deviceId; 51 | } 52 | 53 | /** 54 | * @return the unique key ID for this PreKey. 55 | */ 56 | public int getPreKeyId() { 57 | return preKeyId; 58 | } 59 | 60 | /** 61 | * @return the public key for this PreKey. 62 | */ 63 | public ECPublicKey getPreKey() { 64 | return preKeyPublic; 65 | } 66 | 67 | /** 68 | * @return the unique key ID for this signed prekey. 69 | */ 70 | public int getSignedPreKeyId() { 71 | return signedPreKeyId; 72 | } 73 | 74 | /** 75 | * @return the signed prekey for this PreKeyBundle. 76 | */ 77 | public ECPublicKey getSignedPreKey() { 78 | return signedPreKeyPublic; 79 | } 80 | 81 | /** 82 | * @return the signature over the signed prekey. 83 | */ 84 | public byte[] getSignedPreKeySignature() { 85 | return signedPreKeySignature; 86 | } 87 | 88 | /** 89 | * @return the {@link org.whispersystems.libsignal.IdentityKey} of this PreKeys owner. 90 | */ 91 | public IdentityKey getIdentityKey() { 92 | return identityKey; 93 | } 94 | 95 | /** 96 | * @return the registration ID associated with this PreKey. 97 | */ 98 | public int getRegistrationId() { 99 | return registrationId; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/PreKeyRecord.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import com.google.protobuf.ByteString; 9 | 10 | import org.whispersystems.libsignal.InvalidKeyException; 11 | import org.whispersystems.libsignal.ecc.Curve; 12 | import org.whispersystems.libsignal.ecc.ECKeyPair; 13 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 14 | import org.whispersystems.libsignal.ecc.ECPublicKey; 15 | 16 | import java.io.IOException; 17 | 18 | import static org.whispersystems.libsignal.state.StorageProtos.PreKeyRecordStructure; 19 | 20 | public class PreKeyRecord { 21 | 22 | private PreKeyRecordStructure structure; 23 | 24 | public PreKeyRecord(int id, ECKeyPair keyPair) { 25 | this.structure = PreKeyRecordStructure.newBuilder() 26 | .setId(id) 27 | .setPublicKey(ByteString.copyFrom(keyPair.getPublicKey() 28 | .serialize())) 29 | .setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey() 30 | .serialize())) 31 | .build(); 32 | } 33 | 34 | public PreKeyRecord(byte[] serialized) throws IOException { 35 | this.structure = PreKeyRecordStructure.parseFrom(serialized); 36 | } 37 | 38 | public int getId() { 39 | return this.structure.getId(); 40 | } 41 | 42 | public ECKeyPair getKeyPair() { 43 | try { 44 | ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0); 45 | ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray()); 46 | 47 | return new ECKeyPair(publicKey, privateKey); 48 | } catch (InvalidKeyException e) { 49 | throw new AssertionError(e); 50 | } 51 | } 52 | 53 | public byte[] serialize() { 54 | return this.structure.toByteArray(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/PreKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyIdException; 9 | 10 | /** 11 | * An interface describing the local storage of {@link PreKeyRecord}s. 12 | * 13 | * @author Moxie Marlinspike 14 | */ 15 | public interface PreKeyStore { 16 | 17 | /** 18 | * Load a local PreKeyRecord. 19 | * 20 | * @param preKeyId the ID of the local PreKeyRecord. 21 | * @return the corresponding PreKeyRecord. 22 | * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. 23 | */ 24 | public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException; 25 | 26 | /** 27 | * Store a local PreKeyRecord. 28 | * 29 | * @param preKeyId the ID of the PreKeyRecord to store. 30 | * @param record the PreKeyRecord. 31 | */ 32 | public void storePreKey(int preKeyId, PreKeyRecord record); 33 | 34 | /** 35 | * @param preKeyId A PreKeyRecord ID. 36 | * @return true if the store has a record for the preKeyId, otherwise false. 37 | */ 38 | public boolean containsPreKey(int preKeyId); 39 | 40 | /** 41 | * Delete a PreKeyRecord from local storage. 42 | * 43 | * @param preKeyId The ID of the PreKeyRecord to remove. 44 | */ 45 | public void removePreKey(int preKeyId); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SessionRecord.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import java.io.IOException; 9 | import java.util.Arrays; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | 13 | import static org.whispersystems.libsignal.state.StorageProtos.RecordStructure; 14 | import static org.whispersystems.libsignal.state.StorageProtos.SessionStructure; 15 | 16 | /** 17 | * A SessionRecord encapsulates the state of an ongoing session. 18 | * 19 | * @author Moxie Marlinspike 20 | */ 21 | public class SessionRecord { 22 | 23 | private static final int ARCHIVED_STATES_MAX_LENGTH = 40; 24 | 25 | private SessionState sessionState = new SessionState(); 26 | private LinkedList previousStates = new LinkedList<>(); 27 | private boolean fresh = false; 28 | 29 | public SessionRecord() { 30 | this.fresh = true; 31 | } 32 | 33 | public SessionRecord(SessionState sessionState) { 34 | this.sessionState = sessionState; 35 | this.fresh = false; 36 | } 37 | 38 | public SessionRecord(byte[] serialized) throws IOException { 39 | RecordStructure record = RecordStructure.parseFrom(serialized); 40 | this.sessionState = new SessionState(record.getCurrentSession()); 41 | this.fresh = false; 42 | 43 | for (SessionStructure previousStructure : record.getPreviousSessionsList()) { 44 | previousStates.add(new SessionState(previousStructure)); 45 | } 46 | } 47 | 48 | public boolean hasSessionState(int version, byte[] aliceBaseKey) { 49 | if (sessionState.getSessionVersion() == version && 50 | Arrays.equals(aliceBaseKey, sessionState.getAliceBaseKey())) 51 | { 52 | return true; 53 | } 54 | 55 | for (SessionState state : previousStates) { 56 | if (state.getSessionVersion() == version && 57 | Arrays.equals(aliceBaseKey, state.getAliceBaseKey())) 58 | { 59 | return true; 60 | } 61 | } 62 | 63 | return false; 64 | } 65 | 66 | public SessionState getSessionState() { 67 | return sessionState; 68 | } 69 | 70 | /** 71 | * @return the list of all currently maintained "previous" session states. 72 | */ 73 | public List getPreviousSessionStates() { 74 | return previousStates; 75 | } 76 | 77 | public void removePreviousSessionStates() { 78 | previousStates.clear(); 79 | } 80 | 81 | public boolean isFresh() { 82 | return fresh; 83 | } 84 | 85 | /** 86 | * Move the current {@link SessionState} into the list of "previous" session states, 87 | * and replace the current {@link org.whispersystems.libsignal.state.SessionState} 88 | * with a fresh reset instance. 89 | */ 90 | public void archiveCurrentState() { 91 | promoteState(new SessionState()); 92 | } 93 | 94 | public void promoteState(SessionState promotedState) { 95 | this.previousStates.addFirst(sessionState); 96 | this.sessionState = promotedState; 97 | 98 | if (previousStates.size() > ARCHIVED_STATES_MAX_LENGTH) { 99 | previousStates.removeLast(); 100 | } 101 | } 102 | 103 | public void setState(SessionState sessionState) { 104 | this.sessionState = sessionState; 105 | } 106 | 107 | /** 108 | * @return a serialized version of the current SessionRecord. 109 | */ 110 | public byte[] serialize() { 111 | List previousStructures = new LinkedList<>(); 112 | 113 | for (SessionState previousState : previousStates) { 114 | previousStructures.add(previousState.getStructure()); 115 | } 116 | 117 | RecordStructure record = RecordStructure.newBuilder() 118 | .setCurrentSession(sessionState.getStructure()) 119 | .addAllPreviousSessions(previousStructures) 120 | .build(); 121 | 122 | return record.toByteArray(); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SessionStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import org.whispersystems.libsignal.SignalProtocolAddress; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * The interface to the durable store of session state information 14 | * for remote clients. 15 | * 16 | * @author Moxie Marlinspike 17 | */ 18 | public interface SessionStore { 19 | 20 | /** 21 | * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, 22 | * or a new SessionRecord if one does not currently exist. 23 | *

24 | * It is important that implementations return a copy of the current durable information. The 25 | * returned SessionRecord may be modified, but those changes should not have an effect on the 26 | * durable session state (what is returned by subsequent calls to this method) without the 27 | * store method being called here first. 28 | * 29 | * @param address The name and device ID of the remote client. 30 | * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or 31 | * a new SessionRecord if one does not currently exist. 32 | */ 33 | public SessionRecord loadSession(SignalProtocolAddress address); 34 | 35 | /** 36 | * Returns all known devices with active sessions for a recipient 37 | * 38 | * @param name the name of the client. 39 | * @return all known sub-devices with active sessions. 40 | */ 41 | public List getSubDeviceSessions(String name); 42 | 43 | /** 44 | * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. 45 | * @param address the address of the remote client. 46 | * @param record the current SessionRecord for the remote client. 47 | */ 48 | public void storeSession(SignalProtocolAddress address, SessionRecord record); 49 | 50 | /** 51 | * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. 52 | * @param address the address of the remote client. 53 | * @return true if a {@link SessionRecord} exists, false otherwise. 54 | */ 55 | public boolean containsSession(SignalProtocolAddress address); 56 | 57 | /** 58 | * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. 59 | * 60 | * @param address the address of the remote client. 61 | */ 62 | public void deleteSession(SignalProtocolAddress address); 63 | 64 | /** 65 | * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. 66 | * 67 | * @param name the name of the remote client. 68 | */ 69 | public void deleteAllSessions(String name); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SignalProtocolStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | public interface SignalProtocolStore 9 | extends IdentityKeyStore, PreKeyStore, SessionStore, SignedPreKeyStore 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SignedPreKeyRecord.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import com.google.protobuf.ByteString; 9 | 10 | import org.whispersystems.libsignal.InvalidKeyException; 11 | import org.whispersystems.libsignal.ecc.Curve; 12 | import org.whispersystems.libsignal.ecc.ECKeyPair; 13 | import org.whispersystems.libsignal.ecc.ECPrivateKey; 14 | import org.whispersystems.libsignal.ecc.ECPublicKey; 15 | 16 | import java.io.IOException; 17 | 18 | import static org.whispersystems.libsignal.state.StorageProtos.SignedPreKeyRecordStructure; 19 | 20 | public class SignedPreKeyRecord { 21 | 22 | private SignedPreKeyRecordStructure structure; 23 | 24 | public SignedPreKeyRecord(int id, long timestamp, ECKeyPair keyPair, byte[] signature) { 25 | this.structure = SignedPreKeyRecordStructure.newBuilder() 26 | .setId(id) 27 | .setPublicKey(ByteString.copyFrom(keyPair.getPublicKey() 28 | .serialize())) 29 | .setPrivateKey(ByteString.copyFrom(keyPair.getPrivateKey() 30 | .serialize())) 31 | .setSignature(ByteString.copyFrom(signature)) 32 | .setTimestamp(timestamp) 33 | .build(); 34 | } 35 | 36 | public SignedPreKeyRecord(byte[] serialized) throws IOException { 37 | this.structure = SignedPreKeyRecordStructure.parseFrom(serialized); 38 | } 39 | 40 | public int getId() { 41 | return this.structure.getId(); 42 | } 43 | 44 | public long getTimestamp() { 45 | return this.structure.getTimestamp(); 46 | } 47 | 48 | public ECKeyPair getKeyPair() { 49 | try { 50 | ECPublicKey publicKey = Curve.decodePoint(this.structure.getPublicKey().toByteArray(), 0); 51 | ECPrivateKey privateKey = Curve.decodePrivatePoint(this.structure.getPrivateKey().toByteArray()); 52 | 53 | return new ECKeyPair(publicKey, privateKey); 54 | } catch (InvalidKeyException e) { 55 | throw new AssertionError(e); 56 | } 57 | } 58 | 59 | public byte[] getSignature() { 60 | return this.structure.getSignature().toByteArray(); 61 | } 62 | 63 | public byte[] serialize() { 64 | return this.structure.toByteArray(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/SignedPreKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyIdException; 9 | 10 | import java.util.List; 11 | 12 | public interface SignedPreKeyStore { 13 | 14 | 15 | /** 16 | * Load a local SignedPreKeyRecord. 17 | * 18 | * @param signedPreKeyId the ID of the local SignedPreKeyRecord. 19 | * @return the corresponding SignedPreKeyRecord. 20 | * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. 21 | */ 22 | public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException; 23 | 24 | /** 25 | * Load all local SignedPreKeyRecords. 26 | * 27 | * @return All stored SignedPreKeyRecords. 28 | */ 29 | public List loadSignedPreKeys(); 30 | 31 | /** 32 | * Store a local SignedPreKeyRecord. 33 | * 34 | * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. 35 | * @param record the SignedPreKeyRecord. 36 | */ 37 | public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record); 38 | 39 | /** 40 | * @param signedPreKeyId A SignedPreKeyRecord ID. 41 | * @return true if the store has a record for the signedPreKeyId, otherwise false. 42 | */ 43 | public boolean containsSignedPreKey(int signedPreKeyId); 44 | 45 | /** 46 | * Delete a SignedPreKeyRecord from local storage. 47 | * 48 | * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. 49 | */ 50 | public void removeSignedPreKey(int signedPreKeyId); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemoryIdentityKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state.impl; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.IdentityKeyPair; 10 | import org.whispersystems.libsignal.SignalProtocolAddress; 11 | import org.whispersystems.libsignal.state.IdentityKeyStore; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | public class InMemoryIdentityKeyStore implements IdentityKeyStore { 17 | 18 | private final Map trustedKeys = new HashMap<>(); 19 | 20 | private final IdentityKeyPair identityKeyPair; 21 | private final int localRegistrationId; 22 | 23 | public InMemoryIdentityKeyStore(IdentityKeyPair identityKeyPair, int localRegistrationId) { 24 | this.identityKeyPair = identityKeyPair; 25 | this.localRegistrationId = localRegistrationId; 26 | } 27 | 28 | @Override 29 | public IdentityKeyPair getIdentityKeyPair() { 30 | return identityKeyPair; 31 | } 32 | 33 | @Override 34 | public int getLocalRegistrationId() { 35 | return localRegistrationId; 36 | } 37 | 38 | @Override 39 | public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { 40 | IdentityKey existing = trustedKeys.get(address); 41 | 42 | if (!identityKey.equals(existing)) { 43 | trustedKeys.put(address, identityKey); 44 | return true; 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | @Override 51 | public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { 52 | IdentityKey trusted = trustedKeys.get(address); 53 | return (trusted == null || trusted.equals(identityKey)); 54 | } 55 | 56 | @Override 57 | public IdentityKey getIdentity(SignalProtocolAddress address) { 58 | return trustedKeys.get(address); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemoryPreKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state.impl; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyIdException; 9 | import org.whispersystems.libsignal.state.PreKeyRecord; 10 | import org.whispersystems.libsignal.state.PreKeyStore; 11 | 12 | import java.io.IOException; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | public class InMemoryPreKeyStore implements PreKeyStore { 17 | 18 | private final Map store = new HashMap<>(); 19 | 20 | @Override 21 | public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { 22 | try { 23 | if (!store.containsKey(preKeyId)) { 24 | throw new InvalidKeyIdException("No such prekeyrecord!"); 25 | } 26 | 27 | return new PreKeyRecord(store.get(preKeyId)); 28 | } catch (IOException e) { 29 | throw new AssertionError(e); 30 | } 31 | } 32 | 33 | @Override 34 | public void storePreKey(int preKeyId, PreKeyRecord record) { 35 | store.put(preKeyId, record.serialize()); 36 | } 37 | 38 | @Override 39 | public boolean containsPreKey(int preKeyId) { 40 | return store.containsKey(preKeyId); 41 | } 42 | 43 | @Override 44 | public void removePreKey(int preKeyId) { 45 | store.remove(preKeyId); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemorySessionStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state.impl; 7 | 8 | import org.whispersystems.libsignal.SignalProtocolAddress; 9 | import org.whispersystems.libsignal.state.SessionRecord; 10 | import org.whispersystems.libsignal.state.SessionStore; 11 | 12 | import java.io.IOException; 13 | import java.util.HashMap; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | public class InMemorySessionStore implements SessionStore { 19 | 20 | private Map sessions = new HashMap<>(); 21 | 22 | public InMemorySessionStore() {} 23 | 24 | @Override 25 | public synchronized SessionRecord loadSession(SignalProtocolAddress remoteAddress) { 26 | try { 27 | if (containsSession(remoteAddress)) { 28 | return new SessionRecord(sessions.get(remoteAddress)); 29 | } else { 30 | return new SessionRecord(); 31 | } 32 | } catch (IOException e) { 33 | throw new AssertionError(e); 34 | } 35 | } 36 | 37 | @Override 38 | public synchronized List getSubDeviceSessions(String name) { 39 | List deviceIds = new LinkedList<>(); 40 | 41 | for (SignalProtocolAddress key : sessions.keySet()) { 42 | if (key.getName().equals(name) && 43 | key.getDeviceId() != 1) 44 | { 45 | deviceIds.add(key.getDeviceId()); 46 | } 47 | } 48 | 49 | return deviceIds; 50 | } 51 | 52 | @Override 53 | public synchronized void storeSession(SignalProtocolAddress address, SessionRecord record) { 54 | sessions.put(address, record.serialize()); 55 | } 56 | 57 | @Override 58 | public synchronized boolean containsSession(SignalProtocolAddress address) { 59 | return sessions.containsKey(address); 60 | } 61 | 62 | @Override 63 | public synchronized void deleteSession(SignalProtocolAddress address) { 64 | sessions.remove(address); 65 | } 66 | 67 | @Override 68 | public synchronized void deleteAllSessions(String name) { 69 | for (SignalProtocolAddress key : sessions.keySet()) { 70 | if (key.getName().equals(name)) { 71 | sessions.remove(key); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemorySignalProtocolStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state.impl; 7 | 8 | import org.whispersystems.libsignal.SignalProtocolAddress; 9 | import org.whispersystems.libsignal.IdentityKey; 10 | import org.whispersystems.libsignal.IdentityKeyPair; 11 | import org.whispersystems.libsignal.InvalidKeyIdException; 12 | import org.whispersystems.libsignal.state.SignalProtocolStore; 13 | import org.whispersystems.libsignal.state.PreKeyRecord; 14 | import org.whispersystems.libsignal.state.SessionRecord; 15 | import org.whispersystems.libsignal.state.SignedPreKeyRecord; 16 | 17 | import java.util.List; 18 | 19 | public class InMemorySignalProtocolStore implements SignalProtocolStore { 20 | 21 | private final InMemoryPreKeyStore preKeyStore = new InMemoryPreKeyStore(); 22 | private final InMemorySessionStore sessionStore = new InMemorySessionStore(); 23 | private final InMemorySignedPreKeyStore signedPreKeyStore = new InMemorySignedPreKeyStore(); 24 | 25 | private final InMemoryIdentityKeyStore identityKeyStore; 26 | 27 | public InMemorySignalProtocolStore(IdentityKeyPair identityKeyPair, int registrationId) { 28 | this.identityKeyStore = new InMemoryIdentityKeyStore(identityKeyPair, registrationId); 29 | } 30 | 31 | @Override 32 | public IdentityKeyPair getIdentityKeyPair() { 33 | return identityKeyStore.getIdentityKeyPair(); 34 | } 35 | 36 | @Override 37 | public int getLocalRegistrationId() { 38 | return identityKeyStore.getLocalRegistrationId(); 39 | } 40 | 41 | @Override 42 | public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { 43 | return identityKeyStore.saveIdentity(address, identityKey); 44 | } 45 | 46 | @Override 47 | public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { 48 | return identityKeyStore.isTrustedIdentity(address, identityKey, direction); 49 | } 50 | 51 | @Override 52 | public IdentityKey getIdentity(SignalProtocolAddress address) { 53 | return identityKeyStore.getIdentity(address); 54 | } 55 | 56 | @Override 57 | public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { 58 | return preKeyStore.loadPreKey(preKeyId); 59 | } 60 | 61 | @Override 62 | public void storePreKey(int preKeyId, PreKeyRecord record) { 63 | preKeyStore.storePreKey(preKeyId, record); 64 | } 65 | 66 | @Override 67 | public boolean containsPreKey(int preKeyId) { 68 | return preKeyStore.containsPreKey(preKeyId); 69 | } 70 | 71 | @Override 72 | public void removePreKey(int preKeyId) { 73 | preKeyStore.removePreKey(preKeyId); 74 | } 75 | 76 | @Override 77 | public SessionRecord loadSession(SignalProtocolAddress address) { 78 | return sessionStore.loadSession(address); 79 | } 80 | 81 | @Override 82 | public List getSubDeviceSessions(String name) { 83 | return sessionStore.getSubDeviceSessions(name); 84 | } 85 | 86 | @Override 87 | public void storeSession(SignalProtocolAddress address, SessionRecord record) { 88 | sessionStore.storeSession(address, record); 89 | } 90 | 91 | @Override 92 | public boolean containsSession(SignalProtocolAddress address) { 93 | return sessionStore.containsSession(address); 94 | } 95 | 96 | @Override 97 | public void deleteSession(SignalProtocolAddress address) { 98 | sessionStore.deleteSession(address); 99 | } 100 | 101 | @Override 102 | public void deleteAllSessions(String name) { 103 | sessionStore.deleteAllSessions(name); 104 | } 105 | 106 | @Override 107 | public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { 108 | return signedPreKeyStore.loadSignedPreKey(signedPreKeyId); 109 | } 110 | 111 | @Override 112 | public List loadSignedPreKeys() { 113 | return signedPreKeyStore.loadSignedPreKeys(); 114 | } 115 | 116 | @Override 117 | public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { 118 | signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); 119 | } 120 | 121 | @Override 122 | public boolean containsSignedPreKey(int signedPreKeyId) { 123 | return signedPreKeyStore.containsSignedPreKey(signedPreKeyId); 124 | } 125 | 126 | @Override 127 | public void removeSignedPreKey(int signedPreKeyId) { 128 | signedPreKeyStore.removeSignedPreKey(signedPreKeyId); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/state/impl/InMemorySignedPreKeyStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.state.impl; 7 | 8 | import org.whispersystems.libsignal.InvalidKeyIdException; 9 | import org.whispersystems.libsignal.state.SignedPreKeyRecord; 10 | import org.whispersystems.libsignal.state.SignedPreKeyStore; 11 | 12 | import java.io.IOException; 13 | import java.util.HashMap; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | public class InMemorySignedPreKeyStore implements SignedPreKeyStore { 19 | 20 | private final Map store = new HashMap<>(); 21 | 22 | @Override 23 | public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { 24 | try { 25 | if (!store.containsKey(signedPreKeyId)) { 26 | throw new InvalidKeyIdException("No such signedprekeyrecord! " + signedPreKeyId); 27 | } 28 | 29 | return new SignedPreKeyRecord(store.get(signedPreKeyId)); 30 | } catch (IOException e) { 31 | throw new AssertionError(e); 32 | } 33 | } 34 | 35 | @Override 36 | public List loadSignedPreKeys() { 37 | try { 38 | List results = new LinkedList<>(); 39 | 40 | for (byte[] serialized : store.values()) { 41 | results.add(new SignedPreKeyRecord(serialized)); 42 | } 43 | 44 | return results; 45 | } catch (IOException e) { 46 | throw new AssertionError(e); 47 | } 48 | } 49 | 50 | @Override 51 | public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { 52 | store.put(signedPreKeyId, record.serialize()); 53 | } 54 | 55 | @Override 56 | public boolean containsSignedPreKey(int signedPreKeyId) { 57 | return store.containsKey(signedPreKeyId); 58 | } 59 | 60 | @Override 61 | public void removeSignedPreKey(int signedPreKeyId) { 62 | store.remove(signedPreKeyId); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/ByteArrayComparator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.util; 2 | 3 | public abstract class ByteArrayComparator { 4 | 5 | protected int compare(byte[] left, byte[] right) { 6 | for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) { 7 | int a = (left[i] & 0xff); 8 | int b = (right[j] & 0xff); 9 | 10 | if (a != b) { 11 | return a - b; 12 | } 13 | } 14 | 15 | return left.length - right.length; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/Hex.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.util; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * Utility for generating hex dumps. 12 | */ 13 | public class Hex { 14 | 15 | private final static char[] HEX_DIGITS = { 16 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 17 | }; 18 | 19 | public static String toString(byte[] bytes) { 20 | return toString(bytes, 0, bytes.length); 21 | } 22 | 23 | public static String toString(byte[] bytes, int offset, int length) { 24 | StringBuffer buf = new StringBuffer(); 25 | for (int i = 0; i < length; i++) { 26 | appendHexChar(buf, bytes[offset + i]); 27 | buf.append(", "); 28 | } 29 | return buf.toString(); 30 | } 31 | 32 | public static String toStringCondensed(byte[] bytes) { 33 | StringBuffer buf = new StringBuffer(); 34 | for (int i=0;i> 1]; 49 | 50 | for (int i = 0, j = 0; j < len; i++) { 51 | int f = Character.digit(data[j], 16) << 4; 52 | j++; 53 | f = f | Character.digit(data[j], 16); 54 | j++; 55 | out[i] = (byte) (f & 0xFF); 56 | } 57 | 58 | return out; 59 | } 60 | 61 | private static void appendHexChar(StringBuffer buf, int b) { 62 | buf.append("(byte)0x"); 63 | buf.append(HEX_DIGITS[(b >> 4) & 0xf]); 64 | buf.append(HEX_DIGITS[b & 0xf]); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/IdentityKeyComparator.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.util; 2 | 3 | import org.whispersystems.libsignal.IdentityKey; 4 | 5 | import java.util.Comparator; 6 | 7 | public class IdentityKeyComparator extends ByteArrayComparator implements Comparator { 8 | 9 | @Override 10 | public int compare(IdentityKey first, IdentityKey second) { 11 | return compare(first.getPublicKey().serialize(), second.getPublicKey().serialize()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java/src/main/java/org/whispersystems/libsignal/util/KeyHelper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | package org.whispersystems.libsignal.util; 7 | 8 | import org.whispersystems.libsignal.IdentityKey; 9 | import org.whispersystems.libsignal.IdentityKeyPair; 10 | import org.whispersystems.libsignal.InvalidKeyException; 11 | import org.whispersystems.libsignal.ecc.Curve; 12 | import org.whispersystems.libsignal.ecc.ECKeyPair; 13 | import org.whispersystems.libsignal.state.PreKeyRecord; 14 | import org.whispersystems.libsignal.state.SignedPreKeyRecord; 15 | 16 | import java.security.NoSuchAlgorithmException; 17 | import java.security.SecureRandom; 18 | import java.util.LinkedList; 19 | import java.util.List; 20 | 21 | /** 22 | * Helper class for generating keys of different types. 23 | * 24 | * @author Moxie Marlinspike 25 | */ 26 | public class KeyHelper { 27 | 28 | private KeyHelper() {} 29 | 30 | /** 31 | * Generate an identity key pair. Clients should only do this once, 32 | * at install time. 33 | * 34 | * @return the generated IdentityKeyPair. 35 | */ 36 | public static IdentityKeyPair generateIdentityKeyPair() { 37 | ECKeyPair keyPair = Curve.generateKeyPair(); 38 | IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey()); 39 | return new IdentityKeyPair(publicKey, keyPair.getPrivateKey()); 40 | } 41 | 42 | /** 43 | * Generate a registration ID. Clients should only do this once, 44 | * at install time. 45 | * 46 | * @param extendedRange By default (false), the generated registration 47 | * ID is sized to require the minimal possible protobuf 48 | * encoding overhead. Specify true if the caller needs 49 | * the full range of MAX_INT at the cost of slightly 50 | * higher encoding overhead. 51 | * @return the generated registration ID. 52 | */ 53 | public static int generateRegistrationId(boolean extendedRange) { 54 | try { 55 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 56 | if (extendedRange) return secureRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 57 | else return secureRandom.nextInt(16380) + 1; 58 | } catch (NoSuchAlgorithmException e) { 59 | throw new AssertionError(e); 60 | } 61 | } 62 | 63 | public static int getRandomSequence(int max) { 64 | try { 65 | return SecureRandom.getInstance("SHA1PRNG").nextInt(max); 66 | } catch (NoSuchAlgorithmException e) { 67 | throw new AssertionError(e); 68 | } 69 | } 70 | 71 | /** 72 | * Generate a list of PreKeys. Clients should do this at install time, and 73 | * subsequently any time the list of PreKeys stored on the server runs low. 74 | *

75 | * PreKey IDs are shorts, so they will eventually be repeated. Clients should 76 | * store PreKeys in a circular buffer, so that they are repeated as infrequently 77 | * as possible. 78 | * 79 | * @param start The starting PreKey ID, inclusive. 80 | * @param count The number of PreKeys to generate. 81 | * @return the list of generated PreKeyRecords. 82 | */ 83 | public static List generatePreKeys(int start, int count) { 84 | List results = new LinkedList<>(); 85 | 86 | start--; 87 | 88 | for (int i=0;i { 9 | private final T1 v1; 10 | private final T2 v2; 11 | 12 | public Pair(T1 v1, T2 v2) { 13 | this.v1 = v1; 14 | this.v2 = v2; 15 | } 16 | 17 | public T1 first(){ 18 | return v1; 19 | } 20 | 21 | public T2 second(){ 22 | return v2; 23 | } 24 | 25 | public boolean equals(Object o) { 26 | return o instanceof Pair && 27 | equal(((Pair) o).first(), first()) && 28 | equal(((Pair) o).second(), second()); 29 | } 30 | 31 | public int hashCode() { 32 | return first().hashCode() ^ second().hashCode(); 33 | } 34 | 35 | private boolean equal(Object first, Object second) { 36 | if (first == null && second == null) return true; 37 | if (first == null || second == null) return false; 38 | return first.equals(second); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /protobuf/FingerprintProtocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package textsecure; 4 | 5 | option java_package = "org.whispersystems.libsignal.fingerprint"; 6 | option java_outer_classname = "FingerprintProtos"; 7 | 8 | message LogicalFingerprint { 9 | optional bytes content = 1; 10 | // optional bytes identifier = 2; 11 | } 12 | 13 | message CombinedFingerprints { 14 | optional uint32 version = 1; 15 | optional LogicalFingerprint localFingerprint = 2; 16 | optional LogicalFingerprint remoteFingerprint = 3; 17 | } -------------------------------------------------------------------------------- /protobuf/LocalStorageProtocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package textsecure; 4 | 5 | option java_package = "org.whispersystems.libsignal.state"; 6 | option java_outer_classname = "StorageProtos"; 7 | 8 | message SessionStructure { 9 | message Chain { 10 | optional bytes senderRatchetKey = 1; 11 | optional bytes senderRatchetKeyPrivate = 2; 12 | 13 | message ChainKey { 14 | optional uint32 index = 1; 15 | optional bytes key = 2; 16 | } 17 | 18 | optional ChainKey chainKey = 3; 19 | 20 | message MessageKey { 21 | optional uint32 index = 1; 22 | optional bytes cipherKey = 2; 23 | optional bytes macKey = 3; 24 | optional bytes iv = 4; 25 | } 26 | 27 | repeated MessageKey messageKeys = 4; 28 | } 29 | 30 | message PendingKeyExchange { 31 | optional uint32 sequence = 1; 32 | optional bytes localBaseKey = 2; 33 | optional bytes localBaseKeyPrivate = 3; 34 | optional bytes localRatchetKey = 4; 35 | optional bytes localRatchetKeyPrivate = 5; 36 | optional bytes localIdentityKey = 7; 37 | optional bytes localIdentityKeyPrivate = 8; 38 | } 39 | 40 | message PendingPreKey { 41 | optional uint32 preKeyId = 1; 42 | optional int32 signedPreKeyId = 3; 43 | optional bytes baseKey = 2; 44 | } 45 | 46 | optional uint32 sessionVersion = 1; 47 | optional bytes localIdentityPublic = 2; 48 | optional bytes remoteIdentityPublic = 3; 49 | 50 | optional bytes rootKey = 4; 51 | optional uint32 previousCounter = 5; 52 | 53 | optional Chain senderChain = 6; 54 | repeated Chain receiverChains = 7; 55 | 56 | optional PendingKeyExchange pendingKeyExchange = 8; 57 | optional PendingPreKey pendingPreKey = 9; 58 | 59 | optional uint32 remoteRegistrationId = 10; 60 | optional uint32 localRegistrationId = 11; 61 | 62 | optional bool needsRefresh = 12; 63 | optional bytes aliceBaseKey = 13; 64 | } 65 | 66 | message RecordStructure { 67 | optional SessionStructure currentSession = 1; 68 | repeated SessionStructure previousSessions = 2; 69 | } 70 | 71 | message PreKeyRecordStructure { 72 | optional uint32 id = 1; 73 | optional bytes publicKey = 2; 74 | optional bytes privateKey = 3; 75 | } 76 | 77 | message SignedPreKeyRecordStructure { 78 | optional uint32 id = 1; 79 | optional bytes publicKey = 2; 80 | optional bytes privateKey = 3; 81 | optional bytes signature = 4; 82 | optional fixed64 timestamp = 5; 83 | } 84 | 85 | message IdentityKeyPairStructure { 86 | optional bytes publicKey = 1; 87 | optional bytes privateKey = 2; 88 | } 89 | 90 | message SenderKeyStateStructure { 91 | message SenderChainKey { 92 | optional uint32 iteration = 1; 93 | optional bytes seed = 2; 94 | } 95 | 96 | message SenderMessageKey { 97 | optional uint32 iteration = 1; 98 | optional bytes seed = 2; 99 | } 100 | 101 | message SenderSigningKey { 102 | optional bytes public = 1; 103 | optional bytes private = 2; 104 | } 105 | 106 | optional uint32 senderKeyId = 1; 107 | optional SenderChainKey senderChainKey = 2; 108 | optional SenderSigningKey senderSigningKey = 3; 109 | repeated SenderMessageKey senderMessageKeys = 4; 110 | } 111 | 112 | message SenderKeyRecordStructure { 113 | repeated SenderKeyStateStructure senderKeyStates = 1; 114 | } -------------------------------------------------------------------------------- /protobuf/WhisperTextProtocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package textsecure; 4 | 5 | option java_package = "org.whispersystems.libsignal.protocol"; 6 | option java_outer_classname = "SignalProtos"; 7 | 8 | message SignalMessage { 9 | optional bytes ratchetKey = 1; 10 | optional uint32 counter = 2; 11 | optional uint32 previousCounter = 3; 12 | optional bytes ciphertext = 4; 13 | } 14 | 15 | message PreKeySignalMessage { 16 | optional uint32 registrationId = 5; 17 | optional uint32 preKeyId = 1; 18 | optional uint32 signedPreKeyId = 6; 19 | optional bytes baseKey = 2; 20 | optional bytes identityKey = 3; 21 | optional bytes message = 4; // SignalMessage 22 | } 23 | 24 | message KeyExchangeMessage { 25 | optional uint32 id = 1; 26 | optional bytes baseKey = 2; 27 | optional bytes ratchetKey = 3; 28 | optional bytes identityKey = 4; 29 | optional bytes baseKeySignature = 5; 30 | } 31 | 32 | message SenderKeyMessage { 33 | optional uint32 id = 1; 34 | optional uint32 iteration = 2; 35 | optional bytes ciphertext = 3; 36 | } 37 | 38 | message SenderKeyDistributionMessage { 39 | optional uint32 id = 1; 40 | optional uint32 iteration = 2; 41 | optional bytes chainKey = 3; 42 | optional bytes signingKey = 4; 43 | } 44 | 45 | message DeviceConsistencyCodeMessage { 46 | optional uint32 generation = 1; 47 | optional bytes signature = 2; 48 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':java', ':android', ':tests' 2 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/src/test/java/org/whispersystems/libsignal/devices/DeviceConsistencyTest.java: -------------------------------------------------------------------------------- 1 | package org.whispersystems.libsignal.devices; 2 | 3 | import junit.framework.TestCase; 4 | 5 | import org.whispersystems.libsignal.IdentityKey; 6 | import org.whispersystems.libsignal.IdentityKeyPair; 7 | import org.whispersystems.libsignal.InvalidMessageException; 8 | import org.whispersystems.libsignal.protocol.DeviceConsistencyMessage; 9 | import org.whispersystems.libsignal.util.KeyHelper; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | public class DeviceConsistencyTest extends TestCase { 17 | 18 | public void testDeviceConsistency() throws InvalidMessageException { 19 | final IdentityKeyPair deviceOne = KeyHelper.generateIdentityKeyPair(); 20 | final IdentityKeyPair deviceTwo = KeyHelper.generateIdentityKeyPair(); 21 | final IdentityKeyPair deviceThree = KeyHelper.generateIdentityKeyPair(); 22 | 23 | List keyList = new LinkedList() {{ 24 | add(deviceOne.getPublicKey()); 25 | add(deviceTwo.getPublicKey()); 26 | add(deviceThree.getPublicKey()); 27 | }}; 28 | 29 | Collections.shuffle(keyList); 30 | DeviceConsistencyCommitment deviceOneCommitment = new DeviceConsistencyCommitment(1, keyList); 31 | 32 | Collections.shuffle(keyList); 33 | DeviceConsistencyCommitment deviceTwoCommitment = new DeviceConsistencyCommitment(1, keyList); 34 | 35 | Collections.shuffle(keyList); 36 | DeviceConsistencyCommitment deviceThreeCommitment = new DeviceConsistencyCommitment(1, keyList); 37 | 38 | assertTrue(Arrays.equals(deviceOneCommitment.toByteArray(), deviceTwoCommitment.toByteArray())); 39 | assertTrue(Arrays.equals(deviceTwoCommitment.toByteArray(), deviceThreeCommitment.toByteArray())); 40 | 41 | DeviceConsistencyMessage deviceOneMessage = new DeviceConsistencyMessage(deviceOneCommitment, deviceOne); 42 | DeviceConsistencyMessage deviceTwoMessage = new DeviceConsistencyMessage(deviceOneCommitment, deviceTwo); 43 | DeviceConsistencyMessage deviceThreeMessage = new DeviceConsistencyMessage(deviceOneCommitment, deviceThree); 44 | 45 | DeviceConsistencyMessage receivedDeviceOneMessage = new DeviceConsistencyMessage(deviceOneCommitment, deviceOneMessage.getSerialized(), deviceOne.getPublicKey()); 46 | DeviceConsistencyMessage receivedDeviceTwoMessage = new DeviceConsistencyMessage(deviceOneCommitment, deviceTwoMessage.getSerialized(), deviceTwo.getPublicKey()); 47 | DeviceConsistencyMessage receivedDeviceThreeMessage = new DeviceConsistencyMessage(deviceOneCommitment, deviceThreeMessage.getSerialized(), deviceThree.getPublicKey()); 48 | 49 | assertTrue(Arrays.equals(deviceOneMessage.getSignature().getVrfOutput(), receivedDeviceOneMessage.getSignature().getVrfOutput())); 50 | assertTrue(Arrays.equals(deviceTwoMessage.getSignature().getVrfOutput(), receivedDeviceTwoMessage.getSignature().getVrfOutput())); 51 | assertTrue(Arrays.equals(deviceThreeMessage.getSignature().getVrfOutput(), receivedDeviceThreeMessage.getSignature().getVrfOutput())); 52 | 53 | String codeOne = generateCode(deviceOneCommitment, deviceOneMessage, receivedDeviceTwoMessage, receivedDeviceThreeMessage); 54 | String codeTwo = generateCode(deviceTwoCommitment, deviceTwoMessage, receivedDeviceThreeMessage, receivedDeviceOneMessage); 55 | String codeThree = generateCode(deviceThreeCommitment, deviceThreeMessage, receivedDeviceTwoMessage, receivedDeviceOneMessage); 56 | 57 | assertEquals(codeOne, codeTwo); 58 | assertEquals(codeTwo, codeThree); 59 | } 60 | 61 | private String generateCode(DeviceConsistencyCommitment commitment, 62 | DeviceConsistencyMessage... messages) 63 | { 64 | List signatures = new LinkedList<>(); 65 | 66 | for (DeviceConsistencyMessage message : messages) { 67 | signatures.add(message.getSignature()); 68 | } 69 | 70 | return DeviceConsistencyCodeGenerator.generateFor(commitment, signatures); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------