Compare commits
292 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d979593cbb | ||
|
|
0808e00d0e | ||
|
|
2045c828be | ||
|
|
1b3bd32805 | ||
|
|
877f2a25a9 | ||
|
|
eb462f0345 | ||
|
|
102b40543d | ||
|
|
12077c6dad | ||
|
|
7441c191a7 | ||
|
|
b355991b0b | ||
|
|
350eb438c0 | ||
|
|
da7bbf4144 | ||
|
|
a0842f329f | ||
|
|
567224b4a6 | ||
|
|
9b495d8c83 | ||
|
|
8c55ea09d9 | ||
|
|
5264ebed67 | ||
|
|
7f51f9fd5b | ||
|
|
40495a2261 | ||
|
|
a6c1fdd914 | ||
|
|
020d61dd6e | ||
|
|
dee23b266f | ||
|
|
4cf57fbb55 | ||
|
|
85da05397a | ||
|
|
f4e9c4a710 | ||
|
|
28e14f47cf | ||
|
|
d2edda837a | ||
|
|
213715a0dc | ||
|
|
7b0479ff0f | ||
|
|
feabbb33d2 | ||
|
|
d3da409774 | ||
|
|
edb04138c2 | ||
|
|
8bf537bb09 | ||
|
|
b80593b5f5 | ||
|
|
b61e7839f4 | ||
|
|
7b41b1492e | ||
|
|
928b9687ef | ||
|
|
306c127803 | ||
|
|
239a11bfd2 | ||
|
|
de63b0dd96 | ||
|
|
eb4ac40051 | ||
|
|
dda4459e5a | ||
|
|
8726dd51be | ||
|
|
e53bbe8453 | ||
|
|
ecb67cd84f | ||
|
|
861d27279d | ||
|
|
bef5b8f3e9 | ||
|
|
9e74b5c892 | ||
|
|
3597915d17 | ||
|
|
40ce0cebe0 | ||
|
|
26d58047b5 | ||
|
|
7d688846f9 | ||
|
|
acc7c4c1c6 | ||
|
|
530ad7bc86 | ||
|
|
bea3c33223 | ||
|
|
9ef14a0f64 | ||
|
|
c632b32ff8 | ||
|
|
40698212bb | ||
|
|
19ae5043cc | ||
|
|
d1dd50e31c | ||
|
|
23a1c1c8fa | ||
|
|
3a62a8b428 | ||
|
|
cfac27645b | ||
|
|
f6e04d0f89 | ||
|
|
61d18f49ad | ||
|
|
f26f89d63d | ||
|
|
66aad852f8 | ||
|
|
da0eb5a779 | ||
|
|
a82d2dfc5c | ||
|
|
d429f9113b | ||
|
|
8f85eb1822 | ||
|
|
358c923891 | ||
|
|
2d9cd8eb52 | ||
|
|
db1d846833 | ||
|
|
5121ab0eed | ||
|
|
f63f95404e | ||
|
|
622d8975fc | ||
|
|
81365eff36 | ||
|
|
453610c39f | ||
|
|
5ce6dc954a | ||
|
|
c85a8bbb38 | ||
|
|
0f9a6e6296 | ||
|
|
d8cb893681 | ||
|
|
1ad54e7b88 | ||
|
|
0d35e2bfa9 | ||
|
|
983bf672cf | ||
|
|
1c2e1a07f5 | ||
|
|
2d739a324e | ||
|
|
ba1055df8e | ||
|
|
a54d20f3ef | ||
|
|
ea0fa58265 | ||
|
|
359fe280e8 | ||
|
|
34e147838a | ||
|
|
d8e6a93584 | ||
|
|
5ae8a7a8c4 | ||
|
|
0e6773b4b7 | ||
|
|
fb13d33e2e | ||
|
|
92fd8ededd | ||
|
|
8713a85beb | ||
|
|
9b82411c3d | ||
|
|
16764f74fe | ||
|
|
bd889d8fa9 | ||
|
|
d51adab76b | ||
|
|
b990202468 | ||
|
|
7208018097 | ||
|
|
c719a48a2c | ||
|
|
68747142d6 | ||
|
|
7c9282f306 | ||
|
|
fa3cb871d0 | ||
|
|
a19899a11f | ||
|
|
667da3b2cf | ||
|
|
1a86483b7f | ||
|
|
de90222c95 | ||
|
|
7cf84e904a | ||
|
|
12e92b9cdf | ||
|
|
4153c8dae9 | ||
|
|
ef72702f0d | ||
|
|
f5e2010455 | ||
|
|
bc769debe2 | ||
|
|
df1c96a662 | ||
|
|
db356a0ec9 | ||
|
|
468eb3382c | ||
|
|
12d217991c | ||
|
|
5785860631 | ||
|
|
addea8d340 | ||
|
|
59899b1caf | ||
|
|
829097d891 | ||
|
|
d95bb21065 | ||
|
|
0fbe765447 | ||
|
|
f190321e40 | ||
|
|
1cb4d479f1 | ||
|
|
023d776e96 | ||
|
|
ce7b8ab75a | ||
|
|
82bb0c07e8 | ||
|
|
d8d5848dae | ||
|
|
542e1984c1 | ||
|
|
dff6997a65 | ||
|
|
5bfe64752e | ||
|
|
d6d76fa953 | ||
|
|
03ecd79fe0 | ||
|
|
7a3d509ef4 | ||
|
|
7a54f33f68 | ||
|
|
d4b4667d5a | ||
|
|
08d899e2e1 | ||
|
|
716519f4b8 | ||
|
|
02d3760b31 | ||
|
|
521fbc77c6 | ||
|
|
0574ec170a | ||
|
|
cebad39422 | ||
|
|
1d07ca3e6f | ||
|
|
ca8c950553 | ||
|
|
7349378d8d | ||
|
|
dc9a9b14b2 | ||
|
|
df9afc4e7f | ||
|
|
e9a50ce6c3 | ||
|
|
4d52d2ee36 | ||
|
|
c21e5b74f1 | ||
|
|
c5b3b27c49 | ||
|
|
ccb0cc6baf | ||
|
|
ef77dc9d6d | ||
|
|
454673cd0c | ||
|
|
0dc6ec294b | ||
|
|
12dac6ccc3 | ||
|
|
e2f7c1529a | ||
|
|
b715debefc | ||
|
|
4438b4ae69 | ||
|
|
7b3f2c169a | ||
|
|
7ceaf59bcc | ||
|
|
4caff2e511 | ||
|
|
5e8e13ed5a | ||
|
|
4ea481f9dc | ||
|
|
e94fa0d752 | ||
|
|
817070e76f | ||
|
|
92b90cd798 | ||
|
|
33ecc4d690 | ||
|
|
7d5e66eb6e | ||
|
|
d4ac0c077d | ||
|
|
cbe87aa05c | ||
|
|
9e2f82954f | ||
|
|
d4d684b670 | ||
|
|
c43ef8bce0 | ||
|
|
54a882a11d | ||
|
|
73e2f6ce59 | ||
|
|
54d2184c72 | ||
|
|
832763f695 | ||
|
|
40629a3bcf | ||
|
|
5a3daf4846 | ||
|
|
fd1a18d2d0 | ||
|
|
ad5d6d5bb7 | ||
|
|
926d3c929f | ||
|
|
edc20883eb | ||
|
|
0d7363e36e | ||
|
|
ca6d8a8a0d | ||
|
|
c414334059 | ||
|
|
24a38985cf | ||
|
|
60f2d4d6b6 | ||
|
|
06659fd98f | ||
|
|
3c9c5213a7 | ||
|
|
a183f8d387 | ||
|
|
216446c55b | ||
|
|
bc143059f6 | ||
|
|
33000582ed | ||
|
|
e651f352bb | ||
|
|
cab4a06974 | ||
|
|
ccc1f5e9d6 | ||
|
|
b860aeff85 | ||
|
|
34c885f08d | ||
|
|
71ab6f5c7d | ||
|
|
61fbf382eb | ||
|
|
8b21f3f7d6 | ||
|
|
941d0089f4 | ||
|
|
8b8c6dd45f | ||
|
|
938545444e | ||
|
|
d827ab1b36 | ||
|
|
4701e59197 | ||
|
|
2b2da84918 | ||
|
|
d229a4274c | ||
|
|
64711771f0 | ||
|
|
ad54d2a05f | ||
|
|
068c40336c | ||
|
|
11cfc4f1a1 | ||
|
|
f51989b23e | ||
|
|
0b4fe84a41 | ||
|
|
b855a1805f | ||
|
|
339193af12 | ||
|
|
574f33c92d | ||
|
|
b6a9eb5bf2 | ||
|
|
54b43b7536 | ||
|
|
37c9fb7cd1 | ||
|
|
667d22bace | ||
|
|
3d782449ed | ||
|
|
003095b08c | ||
|
|
d121d9531e | ||
|
|
42aeca26f1 | ||
|
|
ea8a1bae46 | ||
|
|
054fcdca8d | ||
|
|
278220cf18 | ||
|
|
35eeaa9bd5 | ||
|
|
fbf02603ce | ||
|
|
fa423e4432 | ||
|
|
5caec4a146 | ||
|
|
dc77c43435 | ||
|
|
827298d1a2 | ||
|
|
48f5b932f7 | ||
|
|
90169e9468 | ||
|
|
b5fe378bc9 | ||
|
|
0f53c9d170 | ||
|
|
3474950830 | ||
|
|
39ee363150 | ||
|
|
411e3ceff6 | ||
|
|
b7f8c3b3d3 | ||
|
|
3efa8e6899 | ||
|
|
948f481530 | ||
|
|
716fdefa4c | ||
|
|
2dc893730a | ||
|
|
1af99ce155 | ||
|
|
0850f1b0f1 | ||
|
|
1ddc45fd9c | ||
|
|
977765c80f | ||
|
|
4b2d07ab35 | ||
|
|
003ebe6364 | ||
|
|
2f7c005c23 | ||
|
|
367b481d07 | ||
|
|
5cd8c922d2 | ||
|
|
a2fe8a9d5c | ||
|
|
ee921a8f49 | ||
|
|
e0394b4481 | ||
|
|
de100f5be7 | ||
|
|
a020a57be6 | ||
|
|
28f1a0a636 | ||
|
|
5a807ffc28 | ||
|
|
757cb1c846 | ||
|
|
4f066757e7 | ||
|
|
633aa9b057 | ||
|
|
a5c26b2e16 | ||
|
|
ca561d76ff | ||
|
|
107d999ee7 | ||
|
|
0f6c7660cb | ||
|
|
8ea4db03db | ||
|
|
81ee9e31c5 | ||
|
|
a3e900ecbe | ||
|
|
384fb3b2b5 | ||
|
|
d795aa30b3 | ||
|
|
bac4d63312 | ||
|
|
bf60f90019 | ||
|
|
852ca2ac05 | ||
|
|
038bebfdbb | ||
|
|
3b25b87aa8 | ||
|
|
5a62856e46 | ||
|
|
37a52df4e6 | ||
|
|
d3148b6766 | ||
|
|
6fb85aff6d |
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
language: android
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- build-tools-19.1.0
|
||||
- android-19
|
||||
- extra-android-m2repository
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
|
||||
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,tr_TR:tr,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
|
||||
|
||||
|
||||
[textsecure-official.master]
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.thoughtcrime.securesms"
|
||||
android:versionCode="65"
|
||||
android:versionName="2.0.2">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/>
|
||||
android:versionCode="79"
|
||||
android:versionName="2.1.7">
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
||||
android:label="Access to TextSecure Secrets"
|
||||
@@ -33,7 +31,6 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE"
|
||||
@@ -45,6 +42,9 @@
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/TextSecure.LightTheme">
|
||||
|
||||
<meta-data android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
|
||||
<activity android:name=".RoutingActivity"
|
||||
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
||||
android:launchMode="singleTask"
|
||||
@@ -69,8 +69,9 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="audio/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
@@ -89,14 +90,21 @@
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MmsPreferencesActivity"
|
||||
<activity android:name=".MmsPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ConversationListActivity"
|
||||
android:label="@string/app_name"
|
||||
<activity android:name=".ShareActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ConversationListActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ConversationActivity"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
@@ -118,15 +126,15 @@
|
||||
<activity android:name=".PassphraseCreateActivity"
|
||||
android:label="@string/AndroidManifest__create_passphrase"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
|
||||
android:theme="@style/TextSecure.IntroTheme"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PassphrasePromptActivity"
|
||||
android:label="@string/AndroidManifest__enter_passphrase"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:theme="@style/TextSecure.IntroTheme"
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ContactSelectionActivity"
|
||||
@@ -134,15 +142,15 @@
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".SingleContactSelectionActivity"
|
||||
android:label="@string/AndroidManifest__select_contact"
|
||||
<activity android:name=".NewConversationActivity"
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PushContactSelectionActivity"
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".AutoInitiateActivity"
|
||||
android:theme="@style/TextSecure.Light.Dialog"
|
||||
@@ -164,11 +172,7 @@
|
||||
android:label="@string/AndroidManifest__verify_identity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ReviewIdentitiesActivity"
|
||||
android:label="@string/AndroidManifest__manage_identity_keys"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ReceiveKeyActivity"
|
||||
<activity android:name=".ReceiveKeyActivity"
|
||||
android:label="@string/AndroidManifest__complete_key_exchange"
|
||||
android:theme="@style/TextSecure.Light.Dialog"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
@@ -188,11 +192,24 @@
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DummyActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:enabled="true"
|
||||
android:allowTaskReparenting="true"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:alwaysRetainTaskState="false"
|
||||
android:stateNotNeeded="true"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<service android:enabled="true" android:name=".service.GcmRegistrationService"/>
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
<service android:enabled="true" android:name=".service.KeyCachingService"/>
|
||||
<service android:enabled="true" android:name=".service.SendReceiveService"/>
|
||||
<service android:enabled="true" android:name=".service.RegistrationService"/>
|
||||
<service android:enabled="true" android:name=".gcm.GcmIntentService"/>
|
||||
<service android:enabled="true" android:name=".service.DirectoryRefreshService"/>
|
||||
<service android:enabled="true" android:name=".service.PreKeyService"/>
|
||||
|
||||
<service android:name=".service.QuickResponseService"
|
||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||
@@ -219,7 +236,6 @@
|
||||
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
||||
<category android:name="org.thoughtcrime.securesms" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
@@ -285,8 +301,4 @@
|
||||
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="org.thoughtcrime.securesms.tests" android:label="Tests for My App" />
|
||||
|
||||
</manifest>
|
||||
|
||||
67
BUILDING.md
@@ -1,8 +1,67 @@
|
||||
Building TextSecure
|
||||
===================
|
||||
=====================
|
||||
|
||||
1. Ensure the 'Android Support Repository' is installed from the Android SDK manager.
|
||||
Basics
|
||||
------
|
||||
|
||||
Execute Gradle:
|
||||
TextSecure uses [Gradle](http://gradle.org) to build the project and to maintain
|
||||
dependencies.
|
||||
|
||||
./gradlew build
|
||||
Building TextSecure
|
||||
-------------------
|
||||
|
||||
The following steps should help you (re)build TextSecure from the command line.
|
||||
|
||||
1. Checkout the source somewhere on your filesystem with
|
||||
|
||||
git clone https://github.com/WhisperSystems/TextSecure.git
|
||||
|
||||
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed somewhere on your system.
|
||||
3. Ensure the "Android Support Repository" and "Android SDK Build-tools" are installed from the Android SDK manager.
|
||||
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it.
|
||||
|
||||
sdk.dir=\<path to your sdk installation\>
|
||||
|
||||
5. Execute Gradle:
|
||||
|
||||
./gradlew build
|
||||
|
||||
Re-building native components
|
||||
-----------------------------
|
||||
|
||||
Note: This step is optional; native components are contained as binaries (see [library/libs](library/libs)).
|
||||
|
||||
1. Ensure that the Android NDK is installed.
|
||||
|
||||
Execute ndk-build:
|
||||
|
||||
cd library
|
||||
ndk-build
|
||||
|
||||
Afterwards, execute Gradle as above to re-create the APK.
|
||||
|
||||
Setting up a development environment
|
||||
------------------------------------
|
||||
|
||||
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
|
||||
|
||||
1. Install Android Studio.
|
||||
2. Make sure the "Android Support Repository" is installed in the Android Studio SDK.
|
||||
3. Make sure the latest "Android SDK build-tools" is installed in the Android Studio SDK.
|
||||
4. Create a new Android Studio project. from the Quickstart pannel (use File > Close Project to see it), choose "Checkout from Version Control" then "git".
|
||||
5. Paste the URL for the TextSecure project when prompted (https://github.com/WhisperSystems/TextSecure.git).
|
||||
6. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
|
||||
7. Default config options should be good enough.
|
||||
8. Project initialisation and build should proceed.
|
||||
|
||||
Contributing code
|
||||
-----------------
|
||||
|
||||
Code contributions should be sent via github as pull requests, from feature branches [as explained here](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
Mailing list
|
||||
------------
|
||||
|
||||
Development discussion happens on the whispersystems mailing list.
|
||||
[To join](https://lists.riseup.net/www/info/whispersystems)
|
||||
Send emails to whispersystems@lists.riseup.net
|
||||
|
||||
77
README.md
@@ -1,70 +1,73 @@
|
||||
TextSecure
|
||||
=================
|
||||
# TextSecure [](https://travis-ci.org/WhisperSystems/TextSecure)
|
||||
|
||||
A secure text messaging application for Android.
|
||||
TextSecure is a messaging app for easy private communicate with friends.
|
||||
|
||||
TextSecure is a replacement for the standard text messaging application, allowing you to send and receive text messages as normal. Additionally, TextSecure provides:
|
||||
TextSecure can use either data (WiFi/3G/4G) or SMS to communicate securely, and all TextSecure
|
||||
messages can also be encrypted locally on your device.
|
||||
|
||||
1. *Local Encryption* -- All text messages, regardless of destination, that are sent or received with TextSecure are stored in an encrypted database on your phone.
|
||||
2. *Wire Encryption* -- When communicating with a recipient who is also using TextSecure, text messages are encrypted during transmission.
|
||||
Currently available on the Play store.
|
||||
|
||||
Current BitHub Payment For Commit:
|
||||
=================
|
||||
[](https://whispersystems.org/blog/bithub/)
|
||||
*[](https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms)*
|
||||
|
||||
|
||||
Bug tracker
|
||||
-----------
|
||||
|
||||
Have a bug? Please create an issue here on GitHub!
|
||||
## Contributing Bug reports
|
||||
We use GitHub for bug tracking. Please search the existing issues for your bug and create a new one if the issue is not yet tracked!
|
||||
|
||||
https://github.com/WhisperSystems/TextSecure/issues
|
||||
|
||||
## Contributing Translations
|
||||
Interested in helping to translate TextSecure? Contribute here:
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
https://www.transifex.com/projects/p/textsecure-official/
|
||||
|
||||
Looking for documentation? Check out the wiki!
|
||||
## Contributing Code
|
||||
Instructions on how to setup your development environment and build TextSecure can be found in [BUILDING.md](https://github.com/WhisperSystems/TextSecure/blob/master/BUILDING.md).
|
||||
|
||||
https://github.com/WhisperSystems/TextSecure/wiki
|
||||
If you're new to the TextSecure codebase, we recommend going through our issues and picking out a simple bug to fix (check the "easy" label in our issues) in order to get yourself familiar.
|
||||
|
||||
Mailing list
|
||||
------------
|
||||
For larger changes and feature ideas, we ask that you propose it on the mailing list for a high-level discussion before implementation.
|
||||
|
||||
Have a question? Ask on our mailing list!
|
||||
This repository is set up with [BitHub](https://whispersystems.org/blog/bithub/), so you can make money for committing to TextSecure. The current BitHub price for an accepted pull request is:
|
||||
|
||||
[](https://whispersystems.org/blog/bithub/)
|
||||
|
||||
## Contributing Ideas
|
||||
Have something you want to say about Open Whisper Systems projects or want to be part of the conversation? Get involved in the mailing list!
|
||||
|
||||
whispersystems@lists.riseup.net
|
||||
|
||||
https://lists.riseup.net/www/info/whispersystems
|
||||
|
||||
Translation
|
||||
------------
|
||||
## Contributing Funds
|
||||
[](https://whispersystems.org/blog/bithub/)
|
||||
|
||||
Interested in helping to translate TextSecure? Contribute here:
|
||||
You can add funds to BitHub to directly help further development efforts.
|
||||
|
||||
https://www.transifex.com/projects/p/textsecure-official/
|
||||
Help
|
||||
====
|
||||
## Support
|
||||
For troubleshooting and questions, please visit our support center!
|
||||
|
||||
Downloads
|
||||
------------
|
||||
http://support.whispersystems.org/
|
||||
|
||||
TextSecure can be downloaded from the Play Store here:
|
||||
## Documentation
|
||||
Looking for documentation? Check out the wiki!
|
||||
|
||||
https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms
|
||||
https://github.com/WhisperSystems/TextSecure/wiki
|
||||
|
||||
Cryptography Notice
|
||||
------------
|
||||
# Legal things
|
||||
## Cryptography Notice
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
See <http://www.wassenaar.org/> for more information.
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
License
|
||||
---------------------
|
||||
## License
|
||||
|
||||
Copyright 2011 Whisper Systems
|
||||
Copyright 2013 Open WhisperSystems
|
||||
|
||||
Copyright 2013-2014 Open Whisper Systems
|
||||
|
||||
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
public class CanonicalAddressDatabaseTest extends InstrumentationTestCase {
|
||||
private static final String AMBIGUOUS_NUMBER = "222-3333";
|
||||
private static final String SPECIFIC_NUMBER = "+49 444 222 3333";
|
||||
private static final String EMAIL = "a@b.fom";
|
||||
private static final String SIMILAR_EMAIL = "a@b.com";
|
||||
private static final String GROUP = "__textsecure_group__!000111222333";
|
||||
private static final String SIMILAR_GROUP = "__textsecure_group__!100111222333";
|
||||
private static final String ALPHA = "T-Mobile";
|
||||
private static final String SIMILAR_ALPHA = "T-Mobila";
|
||||
|
||||
private CanonicalAddressDatabase db;
|
||||
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.db = CanonicalAddressDatabase.getInstance(getInstrumentation().getTargetContext());
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw two equivalent numbers (one without locale info, one with full info) at the canonical
|
||||
* address db and see that the caching and DB operations work properly in revealing the right
|
||||
* addresses. This is run twice to ensure cache logic is hit.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testNumberAddressUpdates() throws Exception {
|
||||
final long id = db.getCanonicalAddressId(AMBIGUOUS_NUMBER);
|
||||
|
||||
assertThat(db.getAddressFromId(id)).isEqualTo(AMBIGUOUS_NUMBER);
|
||||
assertThat(db.getCanonicalAddressId(SPECIFIC_NUMBER)).isEqualTo(id);
|
||||
assertThat(db.getAddressFromId(id)).isEqualTo(SPECIFIC_NUMBER);
|
||||
assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
|
||||
|
||||
assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
|
||||
assertThat(db.getAddressFromId(id)).isEqualTo(AMBIGUOUS_NUMBER);
|
||||
assertThat(db.getCanonicalAddressId(SPECIFIC_NUMBER)).isEqualTo(id);
|
||||
assertThat(db.getAddressFromId(id)).isEqualTo(SPECIFIC_NUMBER);
|
||||
assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
|
||||
}
|
||||
|
||||
public void testSimilarNumbers() throws Exception {
|
||||
assertThat(db.getCanonicalAddressId("This is a phone number 222-333-444"))
|
||||
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("222-333-444"))
|
||||
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("222-333-44"))
|
||||
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("222-333-4"))
|
||||
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("+49 222-333-4444"))
|
||||
.isNotEqualTo(db.getCanonicalAddressId("+1 222-333-4444"));
|
||||
|
||||
assertThat(db.getCanonicalAddressId("1 222-333-4444"))
|
||||
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("1 (222) 333-4444"))
|
||||
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("+12223334444"))
|
||||
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("+1 (222) 333.4444"))
|
||||
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
assertThat(db.getCanonicalAddressId("+49 (222) 333.4444"))
|
||||
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
|
||||
|
||||
}
|
||||
|
||||
public void testEmailAddresses() throws Exception {
|
||||
final long emailId = db.getCanonicalAddressId(EMAIL);
|
||||
final long similarEmailId = db.getCanonicalAddressId(SIMILAR_EMAIL);
|
||||
|
||||
assertThat(emailId).isNotEqualTo(similarEmailId);
|
||||
|
||||
assertThat(db.getAddressFromId(emailId)).isEqualTo(EMAIL);
|
||||
assertThat(db.getAddressFromId(similarEmailId)).isEqualTo(SIMILAR_EMAIL);
|
||||
}
|
||||
|
||||
public void testGroups() throws Exception {
|
||||
final long groupId = db.getCanonicalAddressId(GROUP);
|
||||
final long similarGroupId = db.getCanonicalAddressId(SIMILAR_GROUP);
|
||||
|
||||
assertThat(groupId).isNotEqualTo(similarGroupId);
|
||||
|
||||
assertThat(db.getAddressFromId(groupId)).isEqualTo(GROUP);
|
||||
assertThat(db.getAddressFromId(similarGroupId)).isEqualTo(SIMILAR_GROUP);
|
||||
}
|
||||
|
||||
public void testAlpha() throws Exception {
|
||||
final long id = db.getCanonicalAddressId(ALPHA);
|
||||
final long similarId = db.getCanonicalAddressId(SIMILAR_ALPHA);
|
||||
|
||||
assertThat(id).isNotEqualTo(similarId);
|
||||
|
||||
assertThat(db.getAddressFromId(id)).isEqualTo(ALPHA);
|
||||
assertThat(db.getAddressFromId(similarId)).isEqualTo(SIMILAR_ALPHA);
|
||||
}
|
||||
|
||||
public void testIsNumber() throws Exception {
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("+495556666777")).isTrue();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("(222) 333-4444")).isTrue();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("1 (222) 333-4444")).isTrue();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile123")).isTrue();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("333-4444")).isTrue();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("12345")).isTrue();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile")).isFalse();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile1")).isFalse();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("Wherever bank")).isFalse();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("__textsecure_group__!afafafafafaf")).isFalse();
|
||||
assertThat(CanonicalAddressDatabase.isNumberAddress("email@domain.com")).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.*;
|
||||
|
||||
public class MmsReceiverTest extends InstrumentationTestCase {
|
||||
|
||||
private MmsReceiver mmsReceiver;
|
||||
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mmsReceiver = new MmsReceiver(getInstrumentation().getContext());
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
public void testProcessMalformedData() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(SendReceiveService.RECEIVE_MMS_ACTION);
|
||||
intent.putExtra("data", new byte[]{0x00});
|
||||
mmsReceiver.process(null, intent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
public class PhoneNumberFormatterTest extends AndroidTestCase {
|
||||
private static final String LOCAL_NUMBER = "+15555555555";
|
||||
|
||||
public void testFormatNumberE164() throws Exception, InvalidNumberException {
|
||||
assertThat(PhoneNumberFormatter.formatNumber("(555) 555-5555", LOCAL_NUMBER)).isEqualTo(LOCAL_NUMBER);
|
||||
assertThat(PhoneNumberFormatter.formatNumber("555-5555", LOCAL_NUMBER)).isEqualTo(LOCAL_NUMBER);
|
||||
assertThat(PhoneNumberFormatter.formatNumber("(123) 555-5555", LOCAL_NUMBER)).isNotEqualTo(LOCAL_NUMBER);
|
||||
}
|
||||
|
||||
public void testFormatNumberEmail() throws Exception {
|
||||
try {
|
||||
PhoneNumberFormatter.formatNumber("person@domain.com", LOCAL_NUMBER);
|
||||
throw new AssertionFailedError("should have thrown on email");
|
||||
} catch (InvalidNumberException ine) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
public class UtilTest extends AndroidTestCase {
|
||||
|
||||
}
|
||||
2
apntool/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.db
|
||||
*.db.gz
|
||||
2218
apntool/apns.xml
Normal file
76
apntool/apntool.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import sys
|
||||
import argparse
|
||||
import sqlite3
|
||||
import gzip
|
||||
from progressbar import ProgressBar, Counter, Timer
|
||||
from lxml import etree
|
||||
|
||||
parser = argparse.ArgumentParser(prog='apntool', description="""Process Android's apn xml files and drop them into an easily
|
||||
queryable SQLite db. Tested up to version 9 of their APN file.""")
|
||||
parser.add_argument('-v', '--version', action='version', version='%(prog)s v1.0')
|
||||
parser.add_argument('-i', '--input', help='the xml file to parse', default='apns.xml', required=False)
|
||||
parser.add_argument('-o', '--output', help='the sqlite db output file', default='apns.db', required=False)
|
||||
parser.add_argument('--quiet', help='do not show progress or verbose instructions', action='store_true', required=False)
|
||||
parser.add_argument('--no-gzip', help="do not gzip after creation", action='store_true', required=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
connection = sqlite3.connect(args.output)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT SQLITE_VERSION()')
|
||||
version = cursor.fetchone()
|
||||
if not args.quiet:
|
||||
print("SQLite version: %s" % version)
|
||||
print("Opening %s" % args.input)
|
||||
|
||||
cursor.execute("PRAGMA legacy_file_format=ON")
|
||||
cursor.execute("PRAGMA journal_mode=DELETE")
|
||||
cursor.execute("PRAGMA page_size=32768")
|
||||
cursor.execute("VACUUM")
|
||||
cursor.execute("DROP TABLE IF EXISTS apns")
|
||||
cursor.execute("""CREATE TABLE apns(_id INTEGER PRIMARY KEY, mccmnc TEXT, mcc TEXT, mnc TEXT, carrier TEXT, apn TEXT,
|
||||
mmsc TEXT, port INTEGER, type TEXT, protocol TEXT, bearer TEXT, roaming_protocol TEXT,
|
||||
carrier_enabled INTEGER, mmsproxy TEXT, mmsport INTEGER, proxy TEXT, mvno_match_data TEXT,
|
||||
mvno_type TEXT, authtype INTEGER, user TEXT, password TEXT, server TEXT)""")
|
||||
|
||||
apns = etree.parse(args.input)
|
||||
root = apns.getroot()
|
||||
pbar = ProgressBar(widgets=['Processed: ', Counter(), ' apns (', Timer(), ')'], maxval=len(list(root))).start() if not args.quiet else None
|
||||
|
||||
count = 0
|
||||
for apn in root.iter("apn"):
|
||||
sqlvars = ["?" for x in apn.attrib.keys()] + ["?"]
|
||||
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + ["%s%s" % (apn.get("mcc"), apn.get("mnc"))]
|
||||
keys = apn.attrib.keys() + ["mccmnc"]
|
||||
|
||||
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
|
||||
cursor.execute(statement, values)
|
||||
|
||||
count += 1
|
||||
if not args.quiet:
|
||||
pbar.update(count)
|
||||
|
||||
if not args.quiet:
|
||||
pbar.finish()
|
||||
connection.commit()
|
||||
print("Successfully written to %s" % args.output)
|
||||
|
||||
if not args.no_gzip:
|
||||
gzipped_file = "%s.gz" % (args.output,)
|
||||
with open(args.output, 'rb') as orig:
|
||||
with gzip.open(gzipped_file, 'wb') as gzipped:
|
||||
gzipped.writelines(orig)
|
||||
print("Successfully gzipped to %s" % gzipped_file)
|
||||
|
||||
if not args.quiet:
|
||||
print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.")
|
||||
print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.")
|
||||
|
||||
except sqlite3.Error, e:
|
||||
if connection:
|
||||
connection.rollback()
|
||||
print("Error: %s" % e.args[0])
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if connection:
|
||||
connection.close()
|
||||
3
apntool/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
argparse>=1.2.1
|
||||
lxml>=3.3.3
|
||||
progressbar-latest>=2.4
|
||||
BIN
artwork/ic_send.psd
Normal file
|
Before Width: | Height: | Size: 582 KiB After Width: | Height: | Size: 496 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 93 KiB |
BIN
assets/databases/apns.db
Normal file
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
BIN
assets/emoji_0_wrapped.png
Normal file
|
After Width: | Height: | Size: 445 KiB |
BIN
assets/emoji_1_wrapped.png
Normal file
|
After Width: | Height: | Size: 547 KiB |
BIN
assets/emoji_2_wrapped.png
Normal file
|
After Width: | Height: | Size: 660 KiB |
BIN
assets/emoji_3_wrapped.png
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
assets/emoji_4_wrapped.png
Normal file
|
After Width: | Height: | Size: 363 KiB |
43
build.gradle
@@ -3,11 +3,13 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.8.+'
|
||||
classpath 'com.android.tools.build:gradle:0.12.2'
|
||||
classpath files('libs/gradle-witness.jar')
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android'
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -17,19 +19,42 @@ repositories {
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/gson/releases/"
|
||||
}
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/smil/releases/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
|
||||
compile 'com.android.support:support-v4:19.0.1'
|
||||
compile 'com.google.android.gcm:gcm-client:1.0.2'
|
||||
compile 'com.android.support:support-v4:20.0.0'
|
||||
compile 'se.emilsjolander:stickylistheaders:2.2.0'
|
||||
compile 'com.google.android.gms:play-services:5.0.89'
|
||||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
compile 'org.w3c:smil:1.0.0'
|
||||
|
||||
androidTestCompile 'com.squareup:fest-android:1.0.8'
|
||||
|
||||
compile project(':library')
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.actionbarsherlock:actionbarsherlock:5ab04d74101f70024b222e3ff9c87bee151ec43331b4a2134b6cc08cf8565819',
|
||||
'com.android.support:support-v4:81f2b1c2c94efd5a4ec7fcd97b6cdcd00e87a933905c5c86103c7319eb024572',
|
||||
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
|
||||
'com.google.android.gms:play-services:38f326e525830f1d70f60f594ceafcbdf5b312287ddbecd338fd1ed7958a4b1e',
|
||||
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
|
||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
||||
'com.google.protobuf:protobuf-java:ad9769a22989e688a46af4d3accc348cc501ced22118033230542bc916e33f0b',
|
||||
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
|
||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
||||
]
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.0.0'
|
||||
buildToolsVersion '19.1.0'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
@@ -42,11 +67,17 @@ android {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aild.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
androidTest {
|
||||
java.srcDirs = ['androidTest']
|
||||
resources.srcDirs = ['androidTest']
|
||||
aidl.srcDirs = ['androidTest']
|
||||
renderscript.srcDirs = ['androidTest']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
contributing.md
Normal file
@@ -0,0 +1,30 @@
|
||||
##Translations
|
||||
|
||||
Please do not submit issues or pull requests for translation fixes. Anyone can update the translations in [Transifex](https://www.transifex.com/projects/p/textsecure-official/). Please submit your corrections there.
|
||||
|
||||
## Submitting useful bug reports
|
||||
1. Search our issues first to make sure this is not a duplicate.
|
||||
1. Read the [Submitting useful bug reports guide](https://github.com/WhisperSystems/TextSecure/wiki/Submitting-useful-bug-reports) before posting a bug.
|
||||
|
||||
## Development Ideology
|
||||
|
||||
Truths which we believe to be self-evident:
|
||||
|
||||
1. **The answer is not more options.** If you feel compelled to add a
|
||||
preference that's exposed to the user, it's very possible you've made
|
||||
a wrong turn somewhere.
|
||||
1. **The user doesn't know what a key is.** We need to minimize the points
|
||||
at which a user is exposed to this sort of terminology as extremely as
|
||||
possible.
|
||||
1. **There are no power users.** The idea that some users "understand"
|
||||
concepts better than others has proven to be, for the most part, false.
|
||||
If anything, "power users" are more dangerous than the rest, and we
|
||||
should avoid exposing dangerous functionality to them.
|
||||
1. **If it's "like PGP," it's wrong.** PGP is our spirit guide for what
|
||||
not to do.
|
||||
1. **It's an asynchronous world.** Be wary of anything that is
|
||||
anti-asynchronous: ACKs, protocol confirmations, or any protocol-level
|
||||
"advisory" message.
|
||||
1. **There is no such thing as time.** Protocol ideas that require synchronized
|
||||
clocks are doomed to failure.
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sat Dec 21 23:48:05 PST 2013
|
||||
#Mon Jun 09 23:26:49 PDT 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.8.+'
|
||||
classpath 'com.android.tools.build:gradle:0.12.2'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,13 @@ repositories {
|
||||
dependencies {
|
||||
compile 'com.google.protobuf:protobuf-java:2.4.1'
|
||||
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:5.3'
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
||||
compile 'org.whispersystems:gson:2.2.4'
|
||||
compile fileTree(dir: 'libs', include: 'armeabi.jar')
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.0.0'
|
||||
buildToolsVersion '19.1.0'
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
@@ -36,10 +35,11 @@ android {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aild.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
library/jni/Application.mk
Normal file
@@ -0,0 +1 @@
|
||||
APP_ABI := armeabi armeabi-v7a x86
|
||||
@@ -22,11 +22,16 @@
|
||||
#include "curve25519-donna.h"
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePrivateKey
|
||||
(JNIEnv *env, jclass clazz, jbyteArray random)
|
||||
(JNIEnv *env, jclass clazz, jbyteArray random, jboolean ephemeral)
|
||||
{
|
||||
uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
|
||||
|
||||
privateKey[0] &= 248;
|
||||
|
||||
if (ephemeral) {
|
||||
privateKey[0] |= 1;
|
||||
}
|
||||
|
||||
privateKey[31] &= 127;
|
||||
privateKey[31] |= 64;
|
||||
|
||||
|
||||
@@ -720,9 +720,6 @@ curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 32; ++i) e[i] = secret[i];
|
||||
e[0] &= 248;
|
||||
e[31] &= 127;
|
||||
e[31] |= 64;
|
||||
|
||||
fexpand(bp, basepoint);
|
||||
cmult(x, z, e, bp);
|
||||
|
||||
BIN
library/libs/armeabi-v7a/libcurve25519.so
Executable file
BIN
library/libs/armeabi/libcurve25519.so
Executable file
BIN
library/libs/x86/libcurve25519.so
Executable file
@@ -54,6 +54,13 @@ message SessionStructure {
|
||||
|
||||
optional uint32 remoteRegistrationId = 10;
|
||||
optional uint32 localRegistrationId = 11;
|
||||
|
||||
optional bool needsRefresh = 12;
|
||||
}
|
||||
|
||||
message RecordStructure {
|
||||
optional SessionStructure currentSession = 1;
|
||||
repeated SessionStructure previousSessions = 2;
|
||||
}
|
||||
|
||||
message PreKeyRecordStructure {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class DuplicateMessageException extends Exception {
|
||||
public DuplicateMessageException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import android.os.Parcelable;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
/**
|
||||
* A class for representing an identity key.
|
||||
@@ -80,14 +79,7 @@ public class IdentityKey implements Parcelable, SerializableKey {
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
if (publicKey.getType() == Curve.NIST_TYPE) {
|
||||
byte[] versionBytes = {0x01};
|
||||
byte[] encodedKey = publicKey.serialize();
|
||||
|
||||
return Util.combine(versionBytes, encodedKey);
|
||||
} else {
|
||||
return publicKey.serialize();
|
||||
}
|
||||
return publicKey.serialize();
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
/**
|
||||
* Represents a session's active KeyPair.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class KeyPair {
|
||||
|
||||
private PublicKey publicKey;
|
||||
private ECPrivateKey privateKey;
|
||||
|
||||
private final MasterCipher masterCipher;
|
||||
|
||||
public KeyPair(int keyPairId, ECKeyPair keyPair, MasterSecret masterSecret) {
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
this.publicKey = new PublicKey(keyPairId, keyPair.getPublicKey());
|
||||
this.privateKey = keyPair.getPrivateKey();
|
||||
}
|
||||
|
||||
public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException {
|
||||
this.masterCipher = masterCipher;
|
||||
deserialize(bytes);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return publicKey.getId();
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public ECPrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
return serialize();
|
||||
}
|
||||
|
||||
private void deserialize(byte[] bytes) throws InvalidKeyException {
|
||||
this.publicKey = new PublicKey(bytes);
|
||||
byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE];
|
||||
System.arraycopy(bytes, PublicKey.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
|
||||
this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes);
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
byte[] publicKeyBytes = publicKey.serialize();
|
||||
Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes));
|
||||
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
|
||||
return Util.combine(publicKeyBytes, privateKeyBytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class LegacyMessageException extends Exception {
|
||||
public LegacyMessageException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -83,11 +83,11 @@ public class MasterCipher {
|
||||
return new String(decodeAndDecryptBytes(body));
|
||||
}
|
||||
|
||||
public ECPrivateKey decryptKey(int type, byte[] key)
|
||||
public ECPrivateKey decryptKey(byte[] key)
|
||||
throws org.whispersystems.textsecure.crypto.InvalidKeyException
|
||||
{
|
||||
try {
|
||||
return Curve.decodePrivatePoint(type, decryptBytes(key));
|
||||
return Curve.decodePrivatePoint(decryptBytes(key));
|
||||
} catch (InvalidMessageException ime) {
|
||||
throw new org.whispersystems.textsecure.crypto.InvalidKeyException(ime);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -49,7 +48,7 @@ public class PreKeyUtil {
|
||||
|
||||
for (int i=0;i<BATCH_SIZE;i++) {
|
||||
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
||||
ECKeyPair keyPair = Curve25519.generateKeyPair();
|
||||
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
|
||||
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
|
||||
|
||||
record.save();
|
||||
@@ -70,7 +69,7 @@ public class PreKeyUtil {
|
||||
}
|
||||
}
|
||||
|
||||
ECKeyPair keyPair = Curve25519.generateKeyPair();
|
||||
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
|
||||
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
|
||||
|
||||
record.save();
|
||||
@@ -78,38 +77,38 @@ public class PreKeyUtil {
|
||||
return record;
|
||||
}
|
||||
|
||||
public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
|
||||
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
|
||||
File directory = getPreKeysDirectory(context);
|
||||
String[] keyRecordIds = directory.list();
|
||||
|
||||
Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
|
||||
|
||||
for (String keyRecordId : keyRecordIds) {
|
||||
try {
|
||||
if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
|
||||
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
|
||||
}
|
||||
} catch (InvalidKeyIdException e) {
|
||||
Log.w("PreKeyUtil", e);
|
||||
new File(getPreKeysDirectory(context), keyRecordId).delete();
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w("PreKeyUtil", nfe);
|
||||
new File(getPreKeysDirectory(context), keyRecordId).delete();
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
public static void clearPreKeys(Context context) {
|
||||
File directory = getPreKeysDirectory(context);
|
||||
String[] keyRecords = directory.list();
|
||||
|
||||
for (String keyRecord : keyRecords) {
|
||||
new File(directory, keyRecord).delete();
|
||||
}
|
||||
}
|
||||
// public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
|
||||
// List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
|
||||
// File directory = getPreKeysDirectory(context);
|
||||
// String[] keyRecordIds = directory.list();
|
||||
//
|
||||
// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
|
||||
//
|
||||
// for (String keyRecordId : keyRecordIds) {
|
||||
// try {
|
||||
// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
|
||||
// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
|
||||
// }
|
||||
// } catch (InvalidKeyIdException e) {
|
||||
// Log.w("PreKeyUtil", e);
|
||||
// new File(getPreKeysDirectory(context), keyRecordId).delete();
|
||||
// } catch (NumberFormatException nfe) {
|
||||
// Log.w("PreKeyUtil", nfe);
|
||||
// new File(getPreKeysDirectory(context), keyRecordId).delete();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return records;
|
||||
// }
|
||||
//
|
||||
// public static void clearPreKeys(Context context) {
|
||||
// File directory = getPreKeysDirectory(context);
|
||||
// String[] keyRecords = directory.list();
|
||||
//
|
||||
// for (String keyRecord : keyRecords) {
|
||||
// new File(directory, keyRecord).delete();
|
||||
// }
|
||||
// }
|
||||
|
||||
private static void setNextPreKeyId(Context context, int id) {
|
||||
try {
|
||||
@@ -126,7 +125,7 @@ public class PreKeyUtil {
|
||||
try {
|
||||
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
||||
|
||||
if (nextFile.exists()) {
|
||||
if (!nextFile.exists()) {
|
||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
||||
} else {
|
||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
||||
|
||||
@@ -18,31 +18,258 @@ package org.whispersystems.textsecure.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV1;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.storage.SessionState;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
public abstract class SessionCipher {
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
protected static final Object SESSION_LOCK = new Object();
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public abstract CiphertextMessage encrypt(byte[] paddedMessage);
|
||||
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException;
|
||||
public abstract int getRemoteRegistrationId();
|
||||
public class SessionCipher {
|
||||
|
||||
private static final Object SESSION_LOCK = new Object();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final RecipientDevice recipient;
|
||||
|
||||
public static SessionCipher createFor(Context context,
|
||||
MasterSecret masterSecret,
|
||||
RecipientDevice recipient)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
return new SessionCipherV2(context, masterSecret, recipient);
|
||||
} else if (SessionRecordV1.hasSession(context, recipient.getRecipientId())) {
|
||||
return new SessionCipherV1(context, masterSecret, recipient.getRecipient());
|
||||
return new SessionCipher(context, masterSecret, recipient);
|
||||
} else {
|
||||
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SessionCipher(Context context, MasterSecret masterSecret, RecipientDevice recipient) {
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
ChainKey chainKey = sessionState.getSenderChainKey();
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
|
||||
int previousCounter = sessionState.getPreviousCounter();
|
||||
|
||||
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
|
||||
CiphertextMessage ciphertextMessage = new WhisperMessage(messageKeys.getMacKey(),
|
||||
senderEphemeral, chainKey.getIndex(),
|
||||
previousCounter, ciphertextBody);
|
||||
|
||||
if (sessionState.hasPendingPreKey()) {
|
||||
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
|
||||
int localRegistrationId = sessionState.getLocalRegistrationId();
|
||||
|
||||
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
|
||||
pendingPreKey.second,
|
||||
sessionState.getLocalIdentityKey(),
|
||||
(WhisperMessage) ciphertextMessage);
|
||||
}
|
||||
|
||||
sessionState.setSenderChainKey(chainKey.getNextChainKey());
|
||||
sessionRecord.save();
|
||||
|
||||
return ciphertextMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] decodedMessage)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
|
||||
|
||||
try {
|
||||
byte[] plaintext = decrypt(sessionState, decodedMessage);
|
||||
sessionRecord.save();
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SessionCipherV2", e);
|
||||
}
|
||||
|
||||
for (SessionState previousState : previousStates) {
|
||||
try {
|
||||
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
|
||||
byte[] plaintext = decrypt(previousState, decodedMessage);
|
||||
sessionRecord.save();
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SessionCipherV2", e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidMessageException("No valid sessions.");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
if (!sessionState.hasSenderChain()) {
|
||||
throw new InvalidMessageException("Uninitialized session!");
|
||||
}
|
||||
|
||||
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
|
||||
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
|
||||
int counter = ciphertextMessage.getCounter();
|
||||
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
|
||||
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
|
||||
chainKey, counter);
|
||||
|
||||
ciphertextMessage.verifyMac(messageKeys.getMacKey());
|
||||
|
||||
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
|
||||
|
||||
sessionState.clearPendingPreKey();
|
||||
|
||||
return plaintext;
|
||||
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId() {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||
return sessionRecord.getSessionState().getRemoteRegistrationId();
|
||||
}
|
||||
}
|
||||
|
||||
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
if (sessionState.hasReceiverChain(theirEphemeral)) {
|
||||
return sessionState.getReceiverChainKey(theirEphemeral);
|
||||
} else {
|
||||
RootKey rootKey = sessionState.getRootKey();
|
||||
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
|
||||
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
|
||||
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
|
||||
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
|
||||
|
||||
sessionState.setRootKey(senderChain.first);
|
||||
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
|
||||
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
|
||||
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
|
||||
|
||||
return receiverChain.second;
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
|
||||
ECPublicKey theirEphemeral,
|
||||
ChainKey chainKey, int counter)
|
||||
throws InvalidMessageException, DuplicateMessageException
|
||||
{
|
||||
if (chainKey.getIndex() > counter) {
|
||||
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
|
||||
return sessionState.removeMessageKeys(theirEphemeral, counter);
|
||||
} else {
|
||||
throw new DuplicateMessageException("Received message with old counter: " +
|
||||
chainKey.getIndex() + " , " + counter);
|
||||
}
|
||||
}
|
||||
|
||||
if (chainKey.getIndex() - counter > 2000) {
|
||||
throw new InvalidMessageException("Over 2000 messages into the future!");
|
||||
}
|
||||
|
||||
while (chainKey.getIndex() < counter) {
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
sessionState.setMessageKeys(theirEphemeral, messageKeys);
|
||||
chainKey = chainKey.getNextChainKey();
|
||||
}
|
||||
|
||||
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
|
||||
return chainKey.getMessageKeys();
|
||||
}
|
||||
|
||||
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
|
||||
messageKeys.getCipherKey(),
|
||||
messageKeys.getCounter());
|
||||
|
||||
return cipher.doFinal(plaintext);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
|
||||
messageKeys.getCipherKey(),
|
||||
messageKeys.getCounter());
|
||||
return cipher.doFinal(cipherText);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
|
||||
byte[] ivBytes = new byte[16];
|
||||
Conversions.intToByteArray(ivBytes, 0, counter);
|
||||
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
cipher.init(mode, key, iv);
|
||||
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SessionRecordV2 getSessionRecord() {
|
||||
return new SessionRecordV2(context, masterSecret, recipient);
|
||||
}
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||
import org.whispersystems.textsecure.crypto.kdf.NKDF;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV1;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipient;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.SessionKey;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV1;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class SessionCipherV1 extends SessionCipher {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final CanonicalRecipient recipient;
|
||||
|
||||
public SessionCipherV1(Context context,
|
||||
MasterSecret masterSecret,
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
public CiphertextMessage encrypt(byte[] paddedMessageBody) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionCipherContext encryptionContext = getEncryptionContext();
|
||||
byte[] cipherText = getCiphertext(paddedMessageBody,
|
||||
encryptionContext.getSessionKey().getCipherKey(),
|
||||
encryptionContext.getSessionRecord().getCounter());
|
||||
|
||||
encryptionContext.getSessionRecord().setSessionKey(encryptionContext.getSessionKey());
|
||||
encryptionContext.getSessionRecord().incrementCounter();
|
||||
encryptionContext.getSessionRecord().save();
|
||||
|
||||
return new WhisperMessageV1(encryptionContext, cipherText);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] decodedCiphertext) throws InvalidMessageException {
|
||||
synchronized (SESSION_LOCK) {
|
||||
WhisperMessageV1 message = new WhisperMessageV1(decodedCiphertext);
|
||||
SessionCipherContext decryptionContext = getDecryptionContext(message);
|
||||
|
||||
message.verifyMac(decryptionContext);
|
||||
|
||||
byte[] plaintextWithPadding = getPlaintext(message.getBody(),
|
||||
decryptionContext.getSessionKey().getCipherKey(),
|
||||
decryptionContext.getCounter());
|
||||
|
||||
decryptionContext.getRemoteKeyRecord().updateCurrentRemoteKey(decryptionContext.getNextKey());
|
||||
decryptionContext.getRemoteKeyRecord().save();
|
||||
|
||||
decryptionContext.getLocalKeyRecord().advanceKeyIfNecessary(decryptionContext.getRecipientKeyId());
|
||||
decryptionContext.getLocalKeyRecord().save();
|
||||
|
||||
decryptionContext.getSessionRecord().setSessionKey(decryptionContext.getSessionKey());
|
||||
decryptionContext.getSessionRecord().save();
|
||||
|
||||
return plaintextWithPadding;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteRegistrationId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private SessionCipherContext getEncryptionContext() {
|
||||
try {
|
||||
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
|
||||
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
|
||||
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
|
||||
int sessionVersion = records.getSessionRecord().getSessionVersion();
|
||||
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE,
|
||||
records, localKeyId, remoteKeyId);
|
||||
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
|
||||
int counter = records.getSessionRecord().getCounter();
|
||||
|
||||
|
||||
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
|
||||
nextKey, counter, sessionVersion);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SessionCipherContext getDecryptionContext(WhisperMessageV1 message)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
|
||||
int messageVersion = message.getCurrentVersion();
|
||||
int recipientKeyId = message.getReceiverKeyId();
|
||||
int senderKeyId = message.getSenderKeyId();
|
||||
PublicKey nextKey = new PublicKey(message.getNextKeyBytes());
|
||||
int counter = message.getCounter();
|
||||
|
||||
if (messageVersion < records.getSessionRecord().getSessionVersion()) {
|
||||
throw new InvalidMessageException("Message version: " + messageVersion +
|
||||
" but negotiated session version: " +
|
||||
records.getSessionRecord().getSessionVersion());
|
||||
}
|
||||
|
||||
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE,
|
||||
records, recipientKeyId, senderKeyId);
|
||||
|
||||
return new SessionCipherContext(records, sessionKey, senderKeyId,
|
||||
recipientKeyId, nextKey, counter,
|
||||
messageVersion);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter);
|
||||
return cipher.doFinal(message);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter);
|
||||
return cipher.doFinal(cipherText);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
|
||||
byte[] ivBytes = new byte[16];
|
||||
Conversions.mediumToByteArray(ivBytes, 0, counter);
|
||||
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
cipher.init(mode, key, iv);
|
||||
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("AES Not Supported!");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new IllegalArgumentException("NoPadding Not Supported!");
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
Log.w("SessionCipher", e);
|
||||
throw new IllegalArgumentException("Invaid Key?");
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.w("SessionCipher", e);
|
||||
throw new IllegalArgumentException("Bad IV?");
|
||||
}
|
||||
}
|
||||
|
||||
private SessionKey getSessionKey(MasterSecret masterSecret, int mode,
|
||||
KeyRecords records,
|
||||
int localKeyId, int remoteKeyId)
|
||||
throws InvalidKeyIdException, InvalidKeyException
|
||||
{
|
||||
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
|
||||
SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId);
|
||||
|
||||
if (sessionKey != null)
|
||||
return sessionKey;
|
||||
|
||||
DerivedSecrets derivedSecrets = calculateSharedSecret(mode, records, localKeyId, remoteKeyId);
|
||||
|
||||
return new SessionKey(mode, localKeyId, remoteKeyId, derivedSecrets.getCipherKey(),
|
||||
derivedSecrets.getMacKey(), masterSecret);
|
||||
}
|
||||
|
||||
private DerivedSecrets calculateSharedSecret(int mode, KeyRecords records,
|
||||
int localKeyId, int remoteKeyId)
|
||||
throws InvalidKeyIdException, InvalidKeyException
|
||||
{
|
||||
NKDF kdf = new NKDF();
|
||||
KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
|
||||
ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
||||
byte[] sharedSecret = Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey());
|
||||
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
|
||||
|
||||
isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd);
|
||||
|
||||
return kdf.deriveSecrets(sharedSecret, isLowEnd);
|
||||
}
|
||||
|
||||
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
|
||||
throws InvalidKeyIdException
|
||||
{
|
||||
ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
|
||||
ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
||||
|
||||
return localPublic.compareTo(remotePublic) < 0;
|
||||
}
|
||||
|
||||
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
||||
RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient);
|
||||
SessionRecordV1 sessionRecord = new SessionRecordV1(context, masterSecret, recipient);
|
||||
return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord);
|
||||
}
|
||||
|
||||
private static class KeyRecords {
|
||||
|
||||
private final LocalKeyRecord localKeyRecord;
|
||||
private final RemoteKeyRecord remoteKeyRecord;
|
||||
private final SessionRecordV1 sessionRecord;
|
||||
|
||||
public KeyRecords(LocalKeyRecord localKeyRecord,
|
||||
RemoteKeyRecord remoteKeyRecord,
|
||||
SessionRecordV1 sessionRecord)
|
||||
{
|
||||
this.localKeyRecord = localKeyRecord;
|
||||
this.remoteKeyRecord = remoteKeyRecord;
|
||||
this.sessionRecord = sessionRecord;
|
||||
}
|
||||
|
||||
private LocalKeyRecord getLocalKeyRecord() {
|
||||
return localKeyRecord;
|
||||
}
|
||||
|
||||
private RemoteKeyRecord getRemoteKeyRecord() {
|
||||
return remoteKeyRecord;
|
||||
}
|
||||
|
||||
private SessionRecordV1 getSessionRecord() {
|
||||
return sessionRecord;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SessionCipherContext {
|
||||
|
||||
private final LocalKeyRecord localKeyRecord;
|
||||
private final RemoteKeyRecord remoteKeyRecord;
|
||||
private final SessionRecordV1 sessionRecord;
|
||||
private final SessionKey sessionKey;
|
||||
private final int senderKeyId;
|
||||
private final int recipientKeyId;
|
||||
private final PublicKey nextKey;
|
||||
private final int counter;
|
||||
private final int messageVersion;
|
||||
|
||||
public SessionCipherContext(KeyRecords records,
|
||||
SessionKey sessionKey,
|
||||
int senderKeyId,
|
||||
int receiverKeyId,
|
||||
PublicKey nextKey,
|
||||
int counter,
|
||||
int messageVersion)
|
||||
{
|
||||
this.localKeyRecord = records.getLocalKeyRecord();
|
||||
this.remoteKeyRecord = records.getRemoteKeyRecord();
|
||||
this.sessionRecord = records.getSessionRecord();
|
||||
this.sessionKey = sessionKey;
|
||||
this.senderKeyId = senderKeyId;
|
||||
this.recipientKeyId = receiverKeyId;
|
||||
this.nextKey = nextKey;
|
||||
this.counter = counter;
|
||||
this.messageVersion = messageVersion;
|
||||
}
|
||||
|
||||
public LocalKeyRecord getLocalKeyRecord() {
|
||||
return localKeyRecord;
|
||||
}
|
||||
|
||||
public RemoteKeyRecord getRemoteKeyRecord() {
|
||||
return remoteKeyRecord;
|
||||
}
|
||||
|
||||
public SessionRecordV1 getSessionRecord() {
|
||||
return sessionRecord;
|
||||
}
|
||||
|
||||
public SessionKey getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public PublicKey getNextKey() {
|
||||
return nextKey;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
public int getSenderKeyId() {
|
||||
return senderKeyId;
|
||||
}
|
||||
|
||||
public int getRecipientKeyId() {
|
||||
return recipientKeyId;
|
||||
}
|
||||
|
||||
public int getMessageVersion() {
|
||||
return messageVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||