Compare commits
365 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b914d5ca25 | ||
|
|
7411a49572 | ||
|
|
3fcd2bb88d | ||
|
|
978f769d6d | ||
|
|
9d693eef30 | ||
|
|
8f79ba1dd1 | ||
|
|
15613894f0 | ||
|
|
a88fbba49f | ||
|
|
f3f173f653 | ||
|
|
5002a749cf | ||
|
|
4a107483c1 | ||
|
|
9ef1ea283b | ||
|
|
32153ec379 | ||
|
|
ceb82a05ca | ||
|
|
899bf8160d | ||
|
|
49f77c9c06 | ||
|
|
cd5feccdbf | ||
|
|
a1da22cee9 | ||
|
|
a950db74c0 | ||
|
|
b0891fa986 | ||
|
|
b57763f397 | ||
|
|
de75a98093 | ||
|
|
0d01feebc3 | ||
|
|
0fb7f3b970 | ||
|
|
a9637baddf | ||
|
|
5e61161767 | ||
|
|
83d281de71 | ||
|
|
28f63cf335 | ||
|
|
f3a3aaca60 | ||
|
|
908e8b8746 | ||
|
|
4573f3cb77 | ||
|
|
d72a3ec669 | ||
|
|
3439657bba | ||
|
|
1ad7912e75 | ||
|
|
30aff82341 | ||
|
|
c54a5b613b | ||
|
|
31ad30b3a5 | ||
|
|
4ccb7ebab6 | ||
|
|
bf69a90b69 | ||
|
|
bf19307916 | ||
|
|
d42c4229fd | ||
|
|
31eddbf346 | ||
|
|
e9b383d277 | ||
|
|
eeafb81c90 | ||
|
|
02e27c94f2 | ||
|
|
167386ea49 | ||
|
|
e31994ac77 | ||
|
|
ea9a5decac | ||
|
|
1cb191a6ee | ||
|
|
60737bdd7b | ||
|
|
2dded4888e | ||
|
|
eaf89735b8 | ||
|
|
8fa2f92a91 | ||
|
|
174324e2a0 | ||
|
|
bc3686058a | ||
|
|
0110015c8b | ||
|
|
5ca8a6d421 | ||
|
|
0fc9ff7490 | ||
|
|
08d939b010 | ||
|
|
020920d988 | ||
|
|
bd3d9ac533 | ||
|
|
85670d95ee | ||
|
|
3d1007d101 | ||
|
|
35821d444e | ||
|
|
3cd7c2d8e5 | ||
|
|
5b08791086 | ||
|
|
31b9dcb5eb | ||
|
|
43adc75428 | ||
|
|
67a4523ca7 | ||
|
|
b35b9be0c8 | ||
|
|
9215322846 | ||
|
|
cbebc040cc | ||
|
|
bea26e83da | ||
|
|
a85dbce041 | ||
|
|
baaa3514d4 | ||
|
|
71fdaac1b2 | ||
|
|
fb31319e52 | ||
|
|
08ed90c5ec | ||
|
|
0d102f76cc | ||
|
|
28cb1ed85b | ||
|
|
cd9b20dc9d | ||
|
|
f09abff407 | ||
|
|
f9934bd8e5 | ||
|
|
1182052c7f | ||
|
|
0d06d50a65 | ||
|
|
d9d4ec9d9d | ||
|
|
9a6f65988f | ||
|
|
601e233d47 | ||
|
|
ae178fc4ec | ||
|
|
cafe03a70a | ||
|
|
99f42e2ee1 | ||
|
|
a3f1d9cdfd | ||
|
|
4cab657ebe | ||
|
|
db6f8618e6 | ||
|
|
98af1fb6ee | ||
|
|
ad1d55f12d | ||
|
|
7df49811b7 | ||
|
|
ff2ac8a66e | ||
|
|
53da1f849a | ||
|
|
503d1ef452 | ||
|
|
9accd92757 | ||
|
|
306da92031 | ||
|
|
e7b6a852c5 | ||
|
|
9dfaf19516 | ||
|
|
34aece0b43 | ||
|
|
9768de2d5e | ||
|
|
c3eb0ea9db | ||
|
|
4cdc0a3e61 | ||
|
|
b568ce70b2 | ||
|
|
18b0601990 | ||
|
|
b308996885 | ||
|
|
73d896f378 | ||
|
|
36ec1d84a1 | ||
|
|
8d6b9ae43e | ||
|
|
a95cc0eba2 | ||
|
|
58d101ff2e | ||
|
|
544f06451f | ||
|
|
20cf775b1e | ||
|
|
5fcc135f81 | ||
|
|
9a0ed659f7 | ||
|
|
54612159be | ||
|
|
355d0be78a | ||
|
|
73b75a4a27 | ||
|
|
c4209a65e3 | ||
|
|
9dce376780 | ||
|
|
07c61394e9 | ||
|
|
3e287f930d | ||
|
|
7b1a37bd91 | ||
|
|
2db44a1578 | ||
|
|
006c9aae7b | ||
|
|
b147a90463 | ||
|
|
741171c49f | ||
|
|
c375ed8638 | ||
|
|
238f29c90a | ||
|
|
084f27a2e8 | ||
|
|
27b5bf54cc | ||
|
|
eda393b11c | ||
|
|
c330eef7b9 | ||
|
|
1eb3884b7a | ||
|
|
5ea3b3038e | ||
|
|
641ac9aed9 | ||
|
|
82bd75fb75 | ||
|
|
c94a7b1eff | ||
|
|
4caebdcd06 | ||
|
|
b3cece27d6 | ||
|
|
819982af7b | ||
|
|
42cf53e487 | ||
|
|
e0d2398ca5 | ||
|
|
3f299936bf | ||
|
|
540592d71f | ||
|
|
5a9e5672d3 | ||
|
|
0a23b5fcd5 | ||
|
|
f0c22d593f | ||
|
|
5f5ddd7c26 | ||
|
|
144f269059 | ||
|
|
0d532afd8e | ||
|
|
07fd17ccda | ||
|
|
fcaa3f0d73 | ||
|
|
64b40df15b | ||
|
|
77ff9cece8 | ||
|
|
6326ef73f3 | ||
|
|
f29d1e6269 | ||
|
|
d6c5e92c9d | ||
|
|
811479d168 | ||
|
|
2ed8d333d9 | ||
|
|
79020cd33c | ||
|
|
c8757c2134 | ||
|
|
2a65257182 | ||
|
|
931605a1c4 | ||
|
|
81ae9af2e4 | ||
|
|
e84a0948e9 | ||
|
|
5239b3e8f8 | ||
|
|
a601c56af1 | ||
|
|
5a3c19fe3e | ||
|
|
af45e5d544 | ||
|
|
72af8b11c2 | ||
|
|
a1db221caf | ||
|
|
14b8f97de2 | ||
|
|
d902c12941 | ||
|
|
fe3d91c40c | ||
|
|
31167d11dd | ||
|
|
6e3751a0c5 | ||
|
|
b5941fb3fd | ||
|
|
cfccd367ad | ||
|
|
2c8c6a410c | ||
|
|
2075bba86c | ||
|
|
1d7b47c982 | ||
|
|
89fb80fcc5 | ||
|
|
f1d230ce6e | ||
|
|
f51a5aa7eb | ||
|
|
cf9c9bbfd7 | ||
|
|
b8a3e87f3d | ||
|
|
e5bad2746f | ||
|
|
7316f17674 | ||
|
|
609e69a801 | ||
|
|
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 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.ai binary
|
||||
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,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
|
||||
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,sr:sr
|
||||
|
||||
|
||||
[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="69"
|
||||
android:versionName="2.0.6">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/>
|
||||
android:versionCode="87"
|
||||
android:versionName="2.3.3">
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
||||
android:label="Access to TextSecure Secrets"
|
||||
@@ -33,18 +31,20 @@
|
||||
<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"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE" />
|
||||
|
||||
<application android:name="org.thoughtcrime.securesms.ApplicationListener"
|
||||
<application android:name=".ApplicationContext"
|
||||
android:icon="@drawable/icon"
|
||||
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"
|
||||
@@ -71,7 +71,7 @@
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
@@ -93,11 +93,18 @@
|
||||
<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"/>
|
||||
@@ -107,27 +114,27 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DatabaseMigrationActivity"
|
||||
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
|
||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DatabaseUpgradeActivity"
|
||||
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
|
||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<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"
|
||||
@@ -165,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"/>
|
||||
@@ -189,6 +192,11 @@
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MediaPreviewActivity"
|
||||
android:label="@string/AndroidManifest__media_preview"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DummyActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:enabled="true"
|
||||
@@ -200,13 +208,14 @@
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<activity android:name=".PlayServicesProblemActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<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=".service.DirectoryRefreshService"/>
|
||||
<service android:enabled="true" android:name=".service.PreKeyService"/>
|
||||
<service android:enabled="true" android:name=".gcm.GcmIntentService"/>
|
||||
|
||||
<service android:name=".service.QuickResponseService"
|
||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||
@@ -233,7 +242,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>
|
||||
@@ -299,8 +307,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>
|
||||
|
||||
16
BUILDING.md
@@ -17,7 +17,11 @@ The following steps should help you (re)build TextSecure from the command line.
|
||||
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.
|
||||
3. Ensure that the following packages are installed from the Android SDK manager:
|
||||
* Android SDK Build Tools
|
||||
* SDK Platform
|
||||
* Android Support Repository
|
||||
* Google Repository
|
||||
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\>
|
||||
@@ -29,13 +33,13 @@ The following steps should help you (re)build TextSecure from the command line.
|
||||
Re-building native components
|
||||
-----------------------------
|
||||
|
||||
Note: This step is optional; native components are contained as binaries (see [library/libs](library/libs)).
|
||||
Note: This step is optional; native components are contained as binaries (see [libaxolotl/libs](libaxolotl/libs)).
|
||||
|
||||
1. Ensure that the Android NDK is installed.
|
||||
|
||||
Execute ndk-build:
|
||||
|
||||
cd library
|
||||
cd libaxolotl
|
||||
ndk-build
|
||||
|
||||
Afterwards, execute Gradle as above to re-create the APK.
|
||||
@@ -45,12 +49,12 @@ Setting up a development environment
|
||||
|
||||
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
|
||||
|
||||
1. Install Android Studio
|
||||
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 wethere to open it. Click "yes".
|
||||
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.
|
||||
|
||||
|
||||
80
README.md
@@ -1,73 +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)*
|
||||
|
||||
Building and contributing code
|
||||
==============================
|
||||
Instructions on how to build TextSecure, as well as on how to setup an IDE to modify it can be found in the "BUILDING.md" file.
|
||||
|
||||
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,153 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
||||
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
|
||||
import org.whispersystems.textsecure.api.TextSecureAccountManager;
|
||||
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
|
||||
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.ObjectGraph;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CleanPreKeysJobTest extends AndroidTestCase {
|
||||
|
||||
public void testSignedPreKeyRotationNotRegistered() throws IOException, MasterSecretJob.RequirementNotMetException {
|
||||
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
|
||||
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
|
||||
MasterSecret masterSecret = mock(MasterSecret.class);
|
||||
when(accountManager.getSignedPreKey()).thenReturn(null);
|
||||
|
||||
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
|
||||
|
||||
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
|
||||
objectGraph.inject(cleanPreKeysJob);
|
||||
|
||||
cleanPreKeysJob.onRun(masterSecret);
|
||||
|
||||
verify(accountManager).getSignedPreKey();
|
||||
verifyNoMoreInteractions(signedPreKeyStore);
|
||||
}
|
||||
|
||||
public void testSignedPreKeyEviction() throws Exception {
|
||||
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
|
||||
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
|
||||
SignedPreKeyEntity currentSignedPreKeyEntity = mock(SignedPreKeyEntity.class);
|
||||
MasterSecret masterSecret = mock(MasterSecret.class);
|
||||
|
||||
when(currentSignedPreKeyEntity.getKeyId()).thenReturn(3133);
|
||||
when(accountManager.getSignedPreKey()).thenReturn(currentSignedPreKeyEntity);
|
||||
|
||||
final SignedPreKeyRecord currentRecord = new SignedPreKeyRecord(3133, System.currentTimeMillis(), Curve.generateKeyPair(), new byte[64]);
|
||||
|
||||
List<SignedPreKeyRecord> records = new LinkedList<SignedPreKeyRecord>() {{
|
||||
add(new SignedPreKeyRecord(2, 11, Curve.generateKeyPair(), new byte[32]));
|
||||
add(new SignedPreKeyRecord(4, System.currentTimeMillis() - 100, Curve.generateKeyPair(), new byte[64]));
|
||||
add(currentRecord);
|
||||
add(new SignedPreKeyRecord(3, System.currentTimeMillis() - 90, Curve.generateKeyPair(), new byte[64]));
|
||||
add(new SignedPreKeyRecord(1, 10, Curve.generateKeyPair(), new byte[32]));
|
||||
}};
|
||||
|
||||
when(signedPreKeyStore.loadSignedPreKeys()).thenReturn(records);
|
||||
when(signedPreKeyStore.loadSignedPreKey(eq(3133))).thenReturn(currentRecord);
|
||||
|
||||
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
|
||||
|
||||
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
|
||||
objectGraph.inject(cleanPreKeysJob);
|
||||
|
||||
cleanPreKeysJob.onRun(masterSecret);
|
||||
|
||||
verify(signedPreKeyStore).removeSignedPreKey(eq(1));
|
||||
verify(signedPreKeyStore, times(1)).removeSignedPreKey(anyInt());
|
||||
}
|
||||
|
||||
public void testSignedPreKeyNoEviction() throws Exception {
|
||||
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
|
||||
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
|
||||
SignedPreKeyEntity currentSignedPreKeyEntity = mock(SignedPreKeyEntity.class);
|
||||
|
||||
when(currentSignedPreKeyEntity.getKeyId()).thenReturn(3133);
|
||||
when(accountManager.getSignedPreKey()).thenReturn(currentSignedPreKeyEntity);
|
||||
|
||||
final SignedPreKeyRecord currentRecord = new SignedPreKeyRecord(3133, System.currentTimeMillis(), Curve.generateKeyPair(), new byte[64]);
|
||||
|
||||
List<SignedPreKeyRecord> records = new LinkedList<SignedPreKeyRecord>() {{
|
||||
add(currentRecord);
|
||||
}};
|
||||
|
||||
when(signedPreKeyStore.loadSignedPreKeys()).thenReturn(records);
|
||||
when(signedPreKeyStore.loadSignedPreKey(eq(3133))).thenReturn(currentRecord);
|
||||
|
||||
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
|
||||
|
||||
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
|
||||
objectGraph.inject(cleanPreKeysJob);
|
||||
|
||||
verify(signedPreKeyStore, never()).removeSignedPreKey(anyInt());
|
||||
}
|
||||
|
||||
public void testConnectionError() throws Exception {
|
||||
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
|
||||
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
|
||||
MasterSecret masterSecret = mock(MasterSecret.class);
|
||||
|
||||
when(accountManager.getSignedPreKey()).thenThrow(new PushNetworkException("Connectivity error!"));
|
||||
|
||||
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
|
||||
|
||||
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
|
||||
objectGraph.inject(cleanPreKeysJob);
|
||||
|
||||
try {
|
||||
cleanPreKeysJob.onRun(masterSecret);
|
||||
throw new AssertionError("should have failed!");
|
||||
} catch (IOException e) {
|
||||
assertTrue(cleanPreKeysJob.onShouldRetry(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Module(injects = {CleanPreKeysJob.class})
|
||||
public static class TestModule {
|
||||
private final TextSecureAccountManager accountManager;
|
||||
private final SignedPreKeyStore signedPreKeyStore;
|
||||
|
||||
private TestModule(TextSecureAccountManager accountManager, SignedPreKeyStore signedPreKeyStore) {
|
||||
this.accountManager = accountManager;
|
||||
this.signedPreKeyStore = signedPreKeyStore;
|
||||
}
|
||||
|
||||
@Provides TextSecureAccountManager provideTextSecureAccountManager() {
|
||||
return accountManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
AxolotlStorageModule.SignedPreKeyStoreFactory provideSignedPreKeyStore() {
|
||||
return new AxolotlStorageModule.SignedPreKeyStoreFactory() {
|
||||
@Override
|
||||
public SignedPreKeyStore create(MasterSecret masterSecret) {
|
||||
return signedPreKeyStore;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.api.TextSecureMessageSender;
|
||||
import org.whispersystems.textsecure.api.push.PushAddress;
|
||||
import org.whispersystems.textsecure.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.ObjectGraph;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
|
||||
|
||||
public class DeliveryReceiptJobTest extends AndroidTestCase {
|
||||
|
||||
public void testDelivery() throws IOException {
|
||||
TextSecureMessageSender textSecureMessageSender = mock(TextSecureMessageSender.class);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
DeliveryReceiptJob deliveryReceiptJob = new DeliveryReceiptJob(getContext(),
|
||||
"+14152222222",
|
||||
timestamp, "foo");
|
||||
|
||||
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(textSecureMessageSender));
|
||||
objectGraph.inject(deliveryReceiptJob);
|
||||
|
||||
deliveryReceiptJob.onRun();
|
||||
|
||||
ArgumentCaptor<PushAddress> captor = ArgumentCaptor.forClass(PushAddress.class);
|
||||
verify(textSecureMessageSender).sendDeliveryReceipt(captor.capture(), eq(timestamp));
|
||||
|
||||
assertTrue(captor.getValue().getRelay().equals("foo"));
|
||||
assertTrue(captor.getValue().getNumber().equals("+14152222222"));
|
||||
}
|
||||
|
||||
public void testNetworkError() throws IOException {
|
||||
TextSecureMessageSender textSecureMessageSender = mock(TextSecureMessageSender.class);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
Mockito.doThrow(new PushNetworkException("network error"))
|
||||
.when(textSecureMessageSender)
|
||||
.sendDeliveryReceipt(any(PushAddress.class), eq(timestamp));
|
||||
|
||||
|
||||
DeliveryReceiptJob deliveryReceiptJob = new DeliveryReceiptJob(getContext(),
|
||||
"+14152222222",
|
||||
timestamp, "foo");
|
||||
|
||||
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(textSecureMessageSender));
|
||||
objectGraph.inject(deliveryReceiptJob);
|
||||
|
||||
try {
|
||||
deliveryReceiptJob.onRun();
|
||||
throw new AssertionError();
|
||||
} catch (IOException e) {
|
||||
assertTrue(deliveryReceiptJob.onShouldRetry(e));
|
||||
}
|
||||
|
||||
Mockito.doThrow(new NotFoundException("not found"))
|
||||
.when(textSecureMessageSender)
|
||||
.sendDeliveryReceipt(any(PushAddress.class), eq(timestamp));
|
||||
|
||||
try {
|
||||
deliveryReceiptJob.onRun();
|
||||
throw new AssertionError();
|
||||
} catch (IOException e) {
|
||||
assertFalse(deliveryReceiptJob.onShouldRetry(e));
|
||||
}
|
||||
}
|
||||
|
||||
@Module(injects = DeliveryReceiptJob.class)
|
||||
public static class TestModule {
|
||||
|
||||
private final TextSecureMessageSender textSecureMessageSender;
|
||||
|
||||
public TestModule(TextSecureMessageSender textSecureMessageSender) {
|
||||
this.textSecureMessageSender = textSecureMessageSender;
|
||||
}
|
||||
|
||||
@Provides TextSecureMessageSenderFactory provideTextSecureMessageSenderFactory() {
|
||||
return new TextSecureMessageSenderFactory() {
|
||||
@Override
|
||||
public TextSecureMessageSender create(MasterSecret masterSecret) {
|
||||
return textSecureMessageSender;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.api.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 {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
2
apntool/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.db
|
||||
*.db.gz
|
||||
1759
apntool/apnlists/cyanogenmod.xml
Normal file
2218
apntool/apnlists/hangouts.xml
Normal file
81
apntool/apntool.py
Normal file
@@ -0,0 +1,81 @@
|
||||
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"):
|
||||
if apn.get("mmsc") == None:
|
||||
continue
|
||||
sqlvars = ["?" for x in apn.attrib.keys()] + ["?"]
|
||||
mccmnc = "%s%s" % (apn.get("mcc"), apn.get("mnc"))
|
||||
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + [mccmnc]
|
||||
keys = apn.attrib.keys() + ["mccmnc"]
|
||||
|
||||
cursor.execute("SELECT 1 FROM apns WHERE mccmnc = ? AND apn = ?", [mccmnc, apn.get("apn")])
|
||||
if cursor.fetchone() == None:
|
||||
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
|
||||
6425
artwork/ic_delivery_delivered.ai
Normal file
6323
artwork/ic_delivery_pending.ai
Normal file
BIN
artwork/ic_lock_black_18dp.psd
Normal file
BIN
artwork/ic_lock_white_18dp.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: 323 KiB |
BIN
assets/emoji_1_wrapped.png
Normal file
|
After Width: | Height: | Size: 394 KiB |
BIN
assets/emoji_2_wrapped.png
Normal file
|
After Width: | Height: | Size: 477 KiB |
BIN
assets/emoji_3_wrapped.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
assets/emoji_4_wrapped.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
100
build.gradle
@@ -1,43 +1,109 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo1.maven.org/maven2"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.9.+'
|
||||
classpath 'com.android.tools.build:gradle:0.14.2'
|
||||
classpath files('libs/gradle-witness.jar')
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android'
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: 'strip_play_services.gradle'
|
||||
apply plugin: 'witness'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/gcm-client/releases/"
|
||||
url "https://repo1.maven.org/maven2"
|
||||
}
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/preferencefragment/releases/"
|
||||
}
|
||||
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 'se.emilsjolander:stickylistheaders:2.2.0'
|
||||
compile 'com.google.android.gms:play-services:6.1.71'
|
||||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
compile 'org.w3c:smil:1.0.0'
|
||||
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||
compile 'com.github.chrisbanes.photoview:library:1.2.3'
|
||||
compile 'com.android.support:appcompat-v7:20.0.0'
|
||||
compile 'com.madgag.spongycastle:prov:1.51.0.0'
|
||||
compile 'com.google.zxing:android-integration:3.1.0'
|
||||
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
compile 'com.squareup.dagger:dagger:1.2.2'
|
||||
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
|
||||
|
||||
compile project(':library')
|
||||
compile 'org.whispersystems:jobmanager:0.9.0'
|
||||
compile 'org.whispersystems:libpastelog:1.0.2'
|
||||
|
||||
androidTestCompile 'com.squareup:fest-android:1.0.8'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'
|
||||
|
||||
compile project(':libtextsecure')
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
|
||||
'com.google.android.gms:play-services:32e7d1834a1cf8fa4b17e8d359db580c286e26c1eefbf84fdb9996eac8d74919',
|
||||
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
|
||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
||||
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
|
||||
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
|
||||
'com.android.support:appcompat-v7:736f576ab0b68d27bdf18b1e7931566e6d8254b73965175313e87f8866b91547',
|
||||
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
||||
'com.android.support:support-v4:81f2b1c2c94efd5a4ec7fcd97b6cdcd00e87a933905c5c86103c7319eb024572',
|
||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'org.whispersystems:libpastelog:9798b3c93a91082c2c68542ce5b5c182e18556aebdcb7c8cebbd89eb48ac4047',
|
||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
||||
'org.whispersystems:jobmanager:adb4329b69035053a7b6a48e22a3280235ac405b225ed8679b994612a8e6f5b6',
|
||||
'com.android.support:support-annotations:1aa96ef0cc4a445bfc2f93ccf762305bc57fa107b12afe9d11f3863ae8a11036',
|
||||
]
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.0.2'
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion '21.1.1'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 19
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
android {
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
@@ -48,6 +114,12 @@ android {
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
androidTest {
|
||||
java.srcDirs = ['androidTest/java']
|
||||
resources.srcDirs = ['androidTest/java']
|
||||
aidl.srcDirs = ['androidTest/java']
|
||||
renderscript.srcDirs = ['androidTest/java']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +137,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.equals("lint")) {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
def Properties props = new Properties()
|
||||
def propFile = new File('signing.properties')
|
||||
|
||||
|
||||
@@ -1,3 +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 @@
|
||||
#Mon Mar 10 23:44:05 PDT 2014
|
||||
#Fri Nov 28 10:03:17 PST 2014
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
|
||||
2
libaxolotl/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
/obj
|
||||
85
libaxolotl/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
# Overview
|
||||
|
||||
This is a ratcheting forward secrecy protocol that works in synchronous and asynchronous messaging
|
||||
environments. The protocol overview is available [here](https://github.com/trevp/axolotl/wiki),
|
||||
and the details of the wire format are available [here](https://github.com/WhisperSystems/TextSecure/wiki/ProtocolV2).
|
||||
|
||||
## PreKeys
|
||||
|
||||
This protocol uses a concept called 'PreKeys'. A PreKey is an ECPublicKey and an associated unique
|
||||
ID which are stored together by a server. PreKeys can also be signed.
|
||||
|
||||
At install time, clients generate a single signed PreKey, as well as a large list of unsigned
|
||||
PreKeys, and transmit all of them to the server.
|
||||
|
||||
## Sessions
|
||||
|
||||
The axolotl protocol is session-oriented. Clients establish a "session," which is then used for
|
||||
all subsequent encrypt/decrypt operations. There is no need to ever tear down a session once one
|
||||
has been established.
|
||||
|
||||
Sessions are established in one of three ways:
|
||||
|
||||
1. PreKeyBundles. A client that wishes to send a message to a recipient can establish a session by
|
||||
retrieving a PreKeyBundle for that recipient from the server.
|
||||
1. PreKeyWhisperMessages. A client can receive a PreKeyWhisperMessage from a recipient and use it
|
||||
to establish a session.
|
||||
1. KeyExchangeMessages. Two clients can exchange KeyExchange messages to establish a session.
|
||||
|
||||
## State
|
||||
|
||||
An established session encapsulates a lot of state between two clients. That state is maintained
|
||||
in durable records which need to be kept for the life of the session.
|
||||
|
||||
State is kept in the following places:
|
||||
|
||||
1. Identity State. Clients will need to maintain the state of their own identity key pair, as well
|
||||
as identity keys received from other clients.
|
||||
1. PreKey State. Clients will need to maintain the state of their generated PreKeys.
|
||||
1. Signed PreKey States. Clients will need to maintain the state of their signed PreKeys.
|
||||
1. Session State. Clients will need to maintain the state of the sessions they have established.
|
||||
|
||||
# Using libaxolotl
|
||||
|
||||
## Install time
|
||||
|
||||
At install time, a libaxolotl client needs to generate its identity keys, registration id, and
|
||||
prekeys.
|
||||
|
||||
IdentityKeyPair identityKeyPair = KeyHelper.generateIdentityKeyPair();
|
||||
int registrationId = KeyHelper.generateRegistrationId();
|
||||
List<PreKeyRecord> preKeys = KeyHelper.generatePreKeys(startId, 100);
|
||||
PreKeyRecord lastResortKey = KeyHelper.generateLastResortKey();
|
||||
SignedPreKeyRecord signedPreKey = KeyHelper.generateSignedPreKey(identityKeyPair, 5);
|
||||
|
||||
// Store identityKeyPair somewhere durable and safe.
|
||||
// Store registrationId somewhere durable and safe.
|
||||
|
||||
// Store preKeys in PreKeyStore.
|
||||
// Store signed prekey in SignedPreKeyStore.
|
||||
|
||||
## Building a session
|
||||
|
||||
A libaxolotl client needs to implement four interfaces: IdentityKeyStore, PreKeyStore,
|
||||
SignedPreKeyStore, and SessionStore. These will manage loading and storing of identity,
|
||||
prekeys, signed prekeys, and session state.
|
||||
|
||||
Once those are implemented, building a session is fairly straightforward:
|
||||
|
||||
SessionStore sessionStore = new MySessionStore();
|
||||
PreKeyStore preKeyStore = new MyPreKeyStore();
|
||||
SignedPreKeyStore signedPreKeyStore = new MySignedPreKeyStore();
|
||||
IdentityKeyStore identityStore = new MyIdentityKeyStore();
|
||||
|
||||
// Instantiate a SessionBuilder for a remote recipientId + deviceId tuple.
|
||||
SessionBuilder sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
|
||||
identityStore, recipientId, deviceId);
|
||||
|
||||
// Build a session with a PreKey retrieved from the server.
|
||||
sessionBuilder.process(retrievedPreKey);
|
||||
|
||||
SessionCipher sessionCipher = new SessionCipher(sessionStore, recipientId, deviceId);
|
||||
CiphertextMessage message = sessionCipher.encrypt("Hello world!".getBytes("UTF-8"));
|
||||
|
||||
deliver(message.serialize());
|
||||
44
libaxolotl/build.gradle
Normal file
@@ -0,0 +1,44 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.14.2'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.protobuf:protobuf-java:2.5.0'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 21
|
||||
buildToolsVersion '21.1.1'
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.equals("lint")) {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
27
libaxolotl/jni/Android.mk
Normal file
@@ -0,0 +1,27 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libcurve25519-donna
|
||||
LOCAL_SRC_FILES := curve25519-donna.c
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libcurve25519-ref10
|
||||
LOCAL_SRC_FILES := $(wildcard ed25519/*.c) $(wildcard ed25519/additions/*.c) $(wildcard ed25519/nacl_sha512/*.c)
|
||||
LOCAL_C_INCLUDES := ed25519/nacl_includes ed25519/additions ed25519/sha512 ed25519
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libcurve25519
|
||||
LOCAL_SRC_FILES := curve25519-jni.c
|
||||
LOCAL_C_INCLUDES := ed25519/additions
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libcurve25519-donna libcurve25519-ref10
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
1
libaxolotl/jni/Application.mk
Normal file
@@ -0,0 +1 @@
|
||||
APP_ABI := armeabi armeabi-v7a x86 mips
|
||||
@@ -43,8 +43,7 @@
|
||||
*
|
||||
* This is, almost, a clean room reimplementation from the curve25519 paper. It
|
||||
* uses many of the tricks described therein. Only the crecip function is taken
|
||||
* from the sample implementation.
|
||||
*/
|
||||
* from the sample implementation. */
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
@@ -63,25 +62,23 @@ typedef int64_t limb;
|
||||
* significant first. The value of the field element is:
|
||||
* x[0] + 2^26·x[1] + x^51·x[2] + 2^102·x[3] + ...
|
||||
*
|
||||
* i.e. the limbs are 26, 25, 26, 25, ... bits wide.
|
||||
*/
|
||||
* i.e. the limbs are 26, 25, 26, 25, ... bits wide. */
|
||||
|
||||
/* Sum two numbers: output += in */
|
||||
static void fsum(limb *output, const limb *in) {
|
||||
unsigned i;
|
||||
for (i = 0; i < 10; i += 2) {
|
||||
output[0+i] = (output[0+i] + in[0+i]);
|
||||
output[1+i] = (output[1+i] + in[1+i]);
|
||||
output[0+i] = output[0+i] + in[0+i];
|
||||
output[1+i] = output[1+i] + in[1+i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the difference of two numbers: output = in - output
|
||||
* (note the order of the arguments!)
|
||||
*/
|
||||
* (note the order of the arguments!). */
|
||||
static void fdifference(limb *output, const limb *in) {
|
||||
unsigned i;
|
||||
for (i = 0; i < 10; ++i) {
|
||||
output[i] = (in[i] - output[i]);
|
||||
output[i] = in[i] - output[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +94,8 @@ static void fscalar_product(limb *output, const limb *in, const limb scalar) {
|
||||
*
|
||||
* output must be distinct to both inputs. The inputs are reduced coefficient
|
||||
* form, the output is not.
|
||||
*/
|
||||
*
|
||||
* output[x] <= 14 * the largest product of the input limbs. */
|
||||
static void fproduct(limb *output, const limb *in2, const limb *in) {
|
||||
output[0] = ((limb) ((s32) in2[0])) * ((s32) in[0]);
|
||||
output[1] = ((limb) ((s32) in2[0])) * ((s32) in[1]) +
|
||||
@@ -201,9 +199,15 @@ static void fproduct(limb *output, const limb *in2, const limb *in) {
|
||||
output[18] = 2 * ((limb) ((s32) in2[9])) * ((s32) in[9]);
|
||||
}
|
||||
|
||||
/* Reduce a long form to a short form by taking the input mod 2^255 - 19. */
|
||||
/* Reduce a long form to a short form by taking the input mod 2^255 - 19.
|
||||
*
|
||||
* On entry: |output[i]| < 14*2^54
|
||||
* On exit: |output[0..8]| < 280*2^54 */
|
||||
static void freduce_degree(limb *output) {
|
||||
/* Each of these shifts and adds ends up multiplying the value by 19. */
|
||||
/* Each of these shifts and adds ends up multiplying the value by 19.
|
||||
*
|
||||
* For output[0..8], the absolute entry value is < 14*2^54 and we add, at
|
||||
* most, 19*14*2^54 thus, on exit, |output[0..8]| < 280*2^54. */
|
||||
output[8] += output[18] << 4;
|
||||
output[8] += output[18] << 1;
|
||||
output[8] += output[18];
|
||||
@@ -237,11 +241,13 @@ static void freduce_degree(limb *output) {
|
||||
#error "This code only works on a two's complement system"
|
||||
#endif
|
||||
|
||||
/* return v / 2^26, using only shifts and adds. */
|
||||
/* return v / 2^26, using only shifts and adds.
|
||||
*
|
||||
* On entry: v can take any value. */
|
||||
static inline limb
|
||||
div_by_2_26(const limb v)
|
||||
{
|
||||
/* High word of v; no shift needed*/
|
||||
/* High word of v; no shift needed. */
|
||||
const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32);
|
||||
/* Set to all 1s if v was negative; else set to 0s. */
|
||||
const int32_t sign = ((int32_t) highword) >> 31;
|
||||
@@ -251,7 +257,9 @@ div_by_2_26(const limb v)
|
||||
return (v + roundoff) >> 26;
|
||||
}
|
||||
|
||||
/* return v / (2^25), using only shifts and adds. */
|
||||
/* return v / (2^25), using only shifts and adds.
|
||||
*
|
||||
* On entry: v can take any value. */
|
||||
static inline limb
|
||||
div_by_2_25(const limb v)
|
||||
{
|
||||
@@ -265,6 +273,9 @@ div_by_2_25(const limb v)
|
||||
return (v + roundoff) >> 25;
|
||||
}
|
||||
|
||||
/* return v / (2^25), using only shifts and adds.
|
||||
*
|
||||
* On entry: v can take any value. */
|
||||
static inline s32
|
||||
div_s32_by_2_25(const s32 v)
|
||||
{
|
||||
@@ -274,8 +285,7 @@ div_s32_by_2_25(const s32 v)
|
||||
|
||||
/* Reduce all coefficients of the short form input so that |x| < 2^26.
|
||||
*
|
||||
* On entry: |output[i]| < 2^62
|
||||
*/
|
||||
* On entry: |output[i]| < 280*2^54 */
|
||||
static void freduce_coefficients(limb *output) {
|
||||
unsigned i;
|
||||
|
||||
@@ -283,56 +293,65 @@ static void freduce_coefficients(limb *output) {
|
||||
|
||||
for (i = 0; i < 10; i += 2) {
|
||||
limb over = div_by_2_26(output[i]);
|
||||
/* The entry condition (that |output[i]| < 280*2^54) means that over is, at
|
||||
* most, 280*2^28 in the first iteration of this loop. This is added to the
|
||||
* next limb and we can approximate the resulting bound of that limb by
|
||||
* 281*2^54. */
|
||||
output[i] -= over << 26;
|
||||
output[i+1] += over;
|
||||
|
||||
/* For the first iteration, |output[i+1]| < 281*2^54, thus |over| <
|
||||
* 281*2^29. When this is added to the next limb, the resulting bound can
|
||||
* be approximated as 281*2^54.
|
||||
*
|
||||
* For subsequent iterations of the loop, 281*2^54 remains a conservative
|
||||
* bound and no overflow occurs. */
|
||||
over = div_by_2_25(output[i+1]);
|
||||
output[i+1] -= over << 25;
|
||||
output[i+2] += over;
|
||||
}
|
||||
/* Now |output[10]| < 2 ^ 38 and all other coefficients are reduced. */
|
||||
/* Now |output[10]| < 281*2^29 and all other coefficients are reduced. */
|
||||
output[0] += output[10] << 4;
|
||||
output[0] += output[10] << 1;
|
||||
output[0] += output[10];
|
||||
|
||||
output[10] = 0;
|
||||
|
||||
/* Now output[1..9] are reduced, and |output[0]| < 2^26 + 19 * 2^38
|
||||
* So |over| will be no more than 77825 */
|
||||
/* Now output[1..9] are reduced, and |output[0]| < 2^26 + 19*281*2^29
|
||||
* So |over| will be no more than 2^16. */
|
||||
{
|
||||
limb over = div_by_2_26(output[0]);
|
||||
output[0] -= over << 26;
|
||||
output[1] += over;
|
||||
}
|
||||
|
||||
/* Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 77825
|
||||
* So |over| will be no more than 1. */
|
||||
{
|
||||
/* output[1] fits in 32 bits, so we can use div_s32_by_2_25 here. */
|
||||
s32 over32 = div_s32_by_2_25((s32) output[1]);
|
||||
output[1] -= over32 << 25;
|
||||
output[2] += over32;
|
||||
}
|
||||
|
||||
/* Finally, output[0,1,3..9] are reduced, and output[2] is "nearly reduced":
|
||||
* we have |output[2]| <= 2^26. This is good enough for all of our math,
|
||||
* but it will require an extra freduce_coefficients before fcontract. */
|
||||
/* Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 2^16 < 2^26. The
|
||||
* bound on |output[1]| is sufficient to meet our needs. */
|
||||
}
|
||||
|
||||
/* A helpful wrapper around fproduct: output = in * in2.
|
||||
*
|
||||
* output must be distinct to both inputs. The output is reduced degree and
|
||||
* reduced coefficient.
|
||||
*/
|
||||
* On entry: |in[i]| < 2^27 and |in2[i]| < 2^27.
|
||||
*
|
||||
* output must be distinct to both inputs. The output is reduced degree
|
||||
* (indeed, one need only provide storage for 10 limbs) and |output[i]| < 2^26. */
|
||||
static void
|
||||
fmul(limb *output, const limb *in, const limb *in2) {
|
||||
limb t[19];
|
||||
fproduct(t, in, in2);
|
||||
/* |t[i]| < 14*2^54 */
|
||||
freduce_degree(t);
|
||||
freduce_coefficients(t);
|
||||
/* |t[i]| < 2^26 */
|
||||
memcpy(output, t, sizeof(limb) * 10);
|
||||
}
|
||||
|
||||
/* Square a number: output = in**2
|
||||
*
|
||||
* output must be distinct from the input. The inputs are reduced coefficient
|
||||
* form, the output is not.
|
||||
*
|
||||
* output[x] <= 14 * the largest product of the input limbs. */
|
||||
static void fsquare_inner(limb *output, const limb *in) {
|
||||
output[0] = ((limb) ((s32) in[0])) * ((s32) in[0]);
|
||||
output[1] = 2 * ((limb) ((s32) in[0])) * ((s32) in[1]);
|
||||
@@ -391,12 +410,23 @@ static void fsquare_inner(limb *output, const limb *in) {
|
||||
output[18] = 2 * ((limb) ((s32) in[9])) * ((s32) in[9]);
|
||||
}
|
||||
|
||||
/* fsquare sets output = in^2.
|
||||
*
|
||||
* On entry: The |in| argument is in reduced coefficients form and |in[i]| <
|
||||
* 2^27.
|
||||
*
|
||||
* On exit: The |output| argument is in reduced coefficients form (indeed, one
|
||||
* need only provide storage for 10 limbs) and |out[i]| < 2^26. */
|
||||
static void
|
||||
fsquare(limb *output, const limb *in) {
|
||||
limb t[19];
|
||||
fsquare_inner(t, in);
|
||||
/* |t[i]| < 14*2^54 because the largest product of two limbs will be <
|
||||
* 2^(27+27) and fsquare_inner adds together, at most, 14 of those
|
||||
* products. */
|
||||
freduce_degree(t);
|
||||
freduce_coefficients(t);
|
||||
/* |t[i]| < 2^26 */
|
||||
memcpy(output, t, sizeof(limb) * 10);
|
||||
}
|
||||
|
||||
@@ -417,7 +447,7 @@ fexpand(limb *output, const u8 *input) {
|
||||
F(6, 19, 1, 0x3ffffff);
|
||||
F(7, 22, 3, 0x1ffffff);
|
||||
F(8, 25, 4, 0x3ffffff);
|
||||
F(9, 28, 6, 0x3ffffff);
|
||||
F(9, 28, 6, 0x1ffffff);
|
||||
#undef F
|
||||
}
|
||||
|
||||
@@ -425,60 +455,143 @@ fexpand(limb *output, const u8 *input) {
|
||||
#error "This code only works when >> does sign-extension on negative numbers"
|
||||
#endif
|
||||
|
||||
/* s32_eq returns 0xffffffff iff a == b and zero otherwise. */
|
||||
static s32 s32_eq(s32 a, s32 b) {
|
||||
a = ~(a ^ b);
|
||||
a &= a << 16;
|
||||
a &= a << 8;
|
||||
a &= a << 4;
|
||||
a &= a << 2;
|
||||
a &= a << 1;
|
||||
return a >> 31;
|
||||
}
|
||||
|
||||
/* s32_gte returns 0xffffffff if a >= b and zero otherwise, where a and b are
|
||||
* both non-negative. */
|
||||
static s32 s32_gte(s32 a, s32 b) {
|
||||
a -= b;
|
||||
/* a >= 0 iff a >= b. */
|
||||
return ~(a >> 31);
|
||||
}
|
||||
|
||||
/* Take a fully reduced polynomial form number and contract it into a
|
||||
* little-endian, 32-byte array
|
||||
*/
|
||||
* little-endian, 32-byte array.
|
||||
*
|
||||
* On entry: |input_limbs[i]| < 2^26 */
|
||||
static void
|
||||
fcontract(u8 *output, limb *input) {
|
||||
fcontract(u8 *output, limb *input_limbs) {
|
||||
int i;
|
||||
int j;
|
||||
s32 input[10];
|
||||
s32 mask;
|
||||
|
||||
/* |input_limbs[i]| < 2^26, so it's valid to convert to an s32. */
|
||||
for (i = 0; i < 10; i++) {
|
||||
input[i] = input_limbs[i];
|
||||
}
|
||||
|
||||
for (j = 0; j < 2; ++j) {
|
||||
for (i = 0; i < 9; ++i) {
|
||||
if ((i & 1) == 1) {
|
||||
/* This calculation is a time-invariant way to make input[i] positive
|
||||
by borrowing from the next-larger limb.
|
||||
*/
|
||||
const s32 mask = (s32)(input[i]) >> 31;
|
||||
const s32 carry = -(((s32)(input[i]) & mask) >> 25);
|
||||
input[i] = (s32)(input[i]) + (carry << 25);
|
||||
input[i+1] = (s32)(input[i+1]) - carry;
|
||||
/* This calculation is a time-invariant way to make input[i]
|
||||
* non-negative by borrowing from the next-larger limb. */
|
||||
const s32 mask = input[i] >> 31;
|
||||
const s32 carry = -((input[i] & mask) >> 25);
|
||||
input[i] = input[i] + (carry << 25);
|
||||
input[i+1] = input[i+1] - carry;
|
||||
} else {
|
||||
const s32 mask = (s32)(input[i]) >> 31;
|
||||
const s32 carry = -(((s32)(input[i]) & mask) >> 26);
|
||||
input[i] = (s32)(input[i]) + (carry << 26);
|
||||
input[i+1] = (s32)(input[i+1]) - carry;
|
||||
const s32 mask = input[i] >> 31;
|
||||
const s32 carry = -((input[i] & mask) >> 26);
|
||||
input[i] = input[i] + (carry << 26);
|
||||
input[i+1] = input[i+1] - carry;
|
||||
}
|
||||
}
|
||||
|
||||
/* There's no greater limb for input[9] to borrow from, but we can multiply
|
||||
* by 19 and borrow from input[0], which is valid mod 2^255-19. */
|
||||
{
|
||||
const s32 mask = (s32)(input[9]) >> 31;
|
||||
const s32 carry = -(((s32)(input[9]) & mask) >> 25);
|
||||
input[9] = (s32)(input[9]) + (carry << 25);
|
||||
input[0] = (s32)(input[0]) - (carry * 19);
|
||||
const s32 mask = input[9] >> 31;
|
||||
const s32 carry = -((input[9] & mask) >> 25);
|
||||
input[9] = input[9] + (carry << 25);
|
||||
input[0] = input[0] - (carry * 19);
|
||||
}
|
||||
|
||||
/* After the first iteration, input[1..9] are non-negative and fit within
|
||||
* 25 or 26 bits, depending on position. However, input[0] may be
|
||||
* negative. */
|
||||
}
|
||||
|
||||
/* The first borrow-propagation pass above ended with every limb
|
||||
except (possibly) input[0] non-negative.
|
||||
|
||||
Since each input limb except input[0] is decreased by at most 1
|
||||
by a borrow-propagation pass, the second borrow-propagation pass
|
||||
could only have wrapped around to decrease input[0] again if the
|
||||
first pass left input[0] negative *and* input[1] through input[9]
|
||||
were all zero. In that case, input[1] is now 2^25 - 1, and this
|
||||
last borrow-propagation step will leave input[1] non-negative.
|
||||
*/
|
||||
If input[0] was negative after the first pass, then it was because of a
|
||||
carry from input[9]. On entry, input[9] < 2^26 so the carry was, at most,
|
||||
one, since (2**26-1) >> 25 = 1. Thus input[0] >= -19.
|
||||
|
||||
In the second pass, each limb is decreased by at most one. Thus the second
|
||||
borrow-propagation pass could only have wrapped around to decrease
|
||||
input[0] again if the first pass left input[0] negative *and* input[1]
|
||||
through input[9] were all zero. In that case, input[1] is now 2^25 - 1,
|
||||
and this last borrow-propagation step will leave input[1] non-negative. */
|
||||
{
|
||||
const s32 mask = (s32)(input[0]) >> 31;
|
||||
const s32 carry = -(((s32)(input[0]) & mask) >> 26);
|
||||
input[0] = (s32)(input[0]) + (carry << 26);
|
||||
input[1] = (s32)(input[1]) - carry;
|
||||
const s32 mask = input[0] >> 31;
|
||||
const s32 carry = -((input[0] & mask) >> 26);
|
||||
input[0] = input[0] + (carry << 26);
|
||||
input[1] = input[1] - carry;
|
||||
}
|
||||
|
||||
/* Both passes through the above loop, plus the last 0-to-1 step, are
|
||||
necessary: if input[9] is -1 and input[0] through input[8] are 0,
|
||||
negative values will remain in the array until the end.
|
||||
*/
|
||||
/* All input[i] are now non-negative. However, there might be values between
|
||||
* 2^25 and 2^26 in a limb which is, nominally, 25 bits wide. */
|
||||
for (j = 0; j < 2; j++) {
|
||||
for (i = 0; i < 9; i++) {
|
||||
if ((i & 1) == 1) {
|
||||
const s32 carry = input[i] >> 25;
|
||||
input[i] &= 0x1ffffff;
|
||||
input[i+1] += carry;
|
||||
} else {
|
||||
const s32 carry = input[i] >> 26;
|
||||
input[i] &= 0x3ffffff;
|
||||
input[i+1] += carry;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const s32 carry = input[9] >> 25;
|
||||
input[9] &= 0x1ffffff;
|
||||
input[0] += 19*carry;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the first carry-chain pass, just above, ended up with a carry from
|
||||
* input[9], and that caused input[0] to be out-of-bounds, then input[0] was
|
||||
* < 2^26 + 2*19, because the carry was, at most, two.
|
||||
*
|
||||
* If the second pass carried from input[9] again then input[0] is < 2*19 and
|
||||
* the input[9] -> input[0] carry didn't push input[0] out of bounds. */
|
||||
|
||||
/* It still remains the case that input might be between 2^255-19 and 2^255.
|
||||
* In this case, input[1..9] must take their maximum value and input[0] must
|
||||
* be >= (2^255-19) & 0x3ffffff, which is 0x3ffffed. */
|
||||
mask = s32_gte(input[0], 0x3ffffed);
|
||||
for (i = 1; i < 10; i++) {
|
||||
if ((i & 1) == 1) {
|
||||
mask &= s32_eq(input[i], 0x1ffffff);
|
||||
} else {
|
||||
mask &= s32_eq(input[i], 0x3ffffff);
|
||||
}
|
||||
}
|
||||
|
||||
/* mask is either 0xffffffff (if input >= 2^255-19) and zero otherwise. Thus
|
||||
* this conditionally subtracts 2^255-19. */
|
||||
input[0] -= mask & 0x3ffffed;
|
||||
|
||||
for (i = 1; i < 10; i++) {
|
||||
if ((i & 1) == 1) {
|
||||
input[i] -= mask & 0x1ffffff;
|
||||
} else {
|
||||
input[i] -= mask & 0x3ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
input[1] <<= 2;
|
||||
input[2] <<= 3;
|
||||
@@ -516,7 +629,9 @@ fcontract(u8 *output, limb *input) {
|
||||
* x z: short form, destroyed
|
||||
* xprime zprime: short form, destroyed
|
||||
* qmqp: short form, preserved
|
||||
*/
|
||||
*
|
||||
* On entry and exit, the absolute value of the limbs of all inputs and outputs
|
||||
* are < 2^26. */
|
||||
static void fmonty(limb *x2, limb *z2, /* output 2Q */
|
||||
limb *x3, limb *z3, /* output Q + Q' */
|
||||
limb *x, limb *z, /* input Q */
|
||||
@@ -527,43 +642,69 @@ static void fmonty(limb *x2, limb *z2, /* output 2Q */
|
||||
|
||||
memcpy(origx, x, 10 * sizeof(limb));
|
||||
fsum(x, z);
|
||||
fdifference(z, origx); // does x - z
|
||||
/* |x[i]| < 2^27 */
|
||||
fdifference(z, origx); /* does x - z */
|
||||
/* |z[i]| < 2^27 */
|
||||
|
||||
memcpy(origxprime, xprime, sizeof(limb) * 10);
|
||||
fsum(xprime, zprime);
|
||||
/* |xprime[i]| < 2^27 */
|
||||
fdifference(zprime, origxprime);
|
||||
/* |zprime[i]| < 2^27 */
|
||||
fproduct(xxprime, xprime, z);
|
||||
/* |xxprime[i]| < 14*2^54: the largest product of two limbs will be <
|
||||
* 2^(27+27) and fproduct adds together, at most, 14 of those products.
|
||||
* (Approximating that to 2^58 doesn't work out.) */
|
||||
fproduct(zzprime, x, zprime);
|
||||
/* |zzprime[i]| < 14*2^54 */
|
||||
freduce_degree(xxprime);
|
||||
freduce_coefficients(xxprime);
|
||||
/* |xxprime[i]| < 2^26 */
|
||||
freduce_degree(zzprime);
|
||||
freduce_coefficients(zzprime);
|
||||
/* |zzprime[i]| < 2^26 */
|
||||
memcpy(origxprime, xxprime, sizeof(limb) * 10);
|
||||
fsum(xxprime, zzprime);
|
||||
/* |xxprime[i]| < 2^27 */
|
||||
fdifference(zzprime, origxprime);
|
||||
/* |zzprime[i]| < 2^27 */
|
||||
fsquare(xxxprime, xxprime);
|
||||
/* |xxxprime[i]| < 2^26 */
|
||||
fsquare(zzzprime, zzprime);
|
||||
/* |zzzprime[i]| < 2^26 */
|
||||
fproduct(zzprime, zzzprime, qmqp);
|
||||
/* |zzprime[i]| < 14*2^52 */
|
||||
freduce_degree(zzprime);
|
||||
freduce_coefficients(zzprime);
|
||||
/* |zzprime[i]| < 2^26 */
|
||||
memcpy(x3, xxxprime, sizeof(limb) * 10);
|
||||
memcpy(z3, zzprime, sizeof(limb) * 10);
|
||||
|
||||
fsquare(xx, x);
|
||||
/* |xx[i]| < 2^26 */
|
||||
fsquare(zz, z);
|
||||
/* |zz[i]| < 2^26 */
|
||||
fproduct(x2, xx, zz);
|
||||
/* |x2[i]| < 14*2^52 */
|
||||
freduce_degree(x2);
|
||||
freduce_coefficients(x2);
|
||||
/* |x2[i]| < 2^26 */
|
||||
fdifference(zz, xx); // does zz = xx - zz
|
||||
/* |zz[i]| < 2^27 */
|
||||
memset(zzz + 10, 0, sizeof(limb) * 9);
|
||||
fscalar_product(zzz, zz, 121665);
|
||||
/* |zzz[i]| < 2^(27+17) */
|
||||
/* No need to call freduce_degree here:
|
||||
fscalar_product doesn't increase the degree of its input. */
|
||||
freduce_coefficients(zzz);
|
||||
/* |zzz[i]| < 2^26 */
|
||||
fsum(zzz, xx);
|
||||
/* |zzz[i]| < 2^27 */
|
||||
fproduct(z2, zz, zzz);
|
||||
/* |z2[i]| < 14*2^(26+27) */
|
||||
freduce_degree(z2);
|
||||
freduce_coefficients(z2);
|
||||
/* |z2|i| < 2^26 */
|
||||
}
|
||||
|
||||
/* Conditionally swap two reduced-form limb arrays if 'iswap' is 1, but leave
|
||||
@@ -574,8 +715,7 @@ static void fmonty(limb *x2, limb *z2, /* output 2Q */
|
||||
* wrong results. Also, the two limb arrays must be in reduced-coefficient,
|
||||
* reduced-degree form: the values in a[10..19] or b[10..19] aren't swapped,
|
||||
* and all all values in a[0..9],b[0..9] must have magnitude less than
|
||||
* INT32_MAX.
|
||||
*/
|
||||
* INT32_MAX. */
|
||||
static void
|
||||
swap_conditional(limb a[19], limb b[19], limb iswap) {
|
||||
unsigned i;
|
||||
@@ -592,8 +732,7 @@ swap_conditional(limb a[19], limb b[19], limb iswap) {
|
||||
*
|
||||
* resultx/resultz: the x coordinate of the resulting curve point (short form)
|
||||
* n: a little endian, 32-byte number
|
||||
* q: a point of the curve (short form)
|
||||
*/
|
||||
* q: a point of the curve (short form) */
|
||||
static void
|
||||
cmult(limb *resultx, limb *resultz, const u8 *n, const limb *q) {
|
||||
limb a[19] = {0}, b[19] = {1}, c[19] = {1}, d[19] = {0};
|
||||
@@ -711,8 +850,6 @@ crecip(limb *out, const limb *z) {
|
||||
/* 2^255 - 21 */ fmul(out,t1,z11);
|
||||
}
|
||||
|
||||
int curve25519_donna(u8 *, const u8 *, const u8 *);
|
||||
|
||||
int
|
||||
curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
|
||||
limb bp[10], x[10], z[11], zmone[10];
|
||||
@@ -720,12 +857,14 @@ 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);
|
||||
crecip(zmone, z);
|
||||
fmul(z, x, zmone);
|
||||
freduce_coefficients(z);
|
||||
fcontract(mypublic, z);
|
||||
return 0;
|
||||
}
|
||||
109
libaxolotl/jni/curve25519-jni.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (C) 2013-2014 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/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include "curve25519-donna.h"
|
||||
#include "curve_sigs.h"
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_libaxolotl_ecc_Curve25519_generatePrivateKey
|
||||
(JNIEnv *env, jclass clazz, jbyteArray random)
|
||||
{
|
||||
uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
|
||||
|
||||
privateKey[0] &= 248;
|
||||
privateKey[31] &= 127;
|
||||
privateKey[31] |= 64;
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, random, privateKey, 0);
|
||||
|
||||
return random;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_libaxolotl_ecc_Curve25519_generatePublicKey
|
||||
(JNIEnv *env, jclass clazz, jbyteArray privateKey)
|
||||
{
|
||||
static const uint8_t basepoint[32] = {9};
|
||||
|
||||
jbyteArray publicKey = (*env)->NewByteArray(env, 32);
|
||||
uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0);
|
||||
uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0);
|
||||
|
||||
curve25519_donna(publicKeyBytes, privateKeyBytes, basepoint);
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0);
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_libaxolotl_ecc_Curve25519_calculateAgreement
|
||||
(JNIEnv *env, jclass clazz, jbyteArray privateKey, jbyteArray publicKey)
|
||||
{
|
||||
jbyteArray sharedKey = (*env)->NewByteArray(env, 32);
|
||||
uint8_t* sharedKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, sharedKey, 0);
|
||||
uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0);
|
||||
uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0);
|
||||
|
||||
curve25519_donna(sharedKeyBytes, privateKeyBytes, publicKeyBytes);
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, sharedKey, sharedKeyBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0);
|
||||
|
||||
return sharedKey;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_libaxolotl_ecc_Curve25519_calculateSignature
|
||||
(JNIEnv *env, jclass clazz, jbyteArray random, jbyteArray privateKey, jbyteArray message)
|
||||
{
|
||||
jbyteArray signature = (*env)->NewByteArray(env, 64);
|
||||
uint8_t* signatureBytes = (uint8_t*)(*env)->GetByteArrayElements(env, signature, 0);
|
||||
uint8_t* randomBytes = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
|
||||
uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0);
|
||||
uint8_t* messageBytes = (uint8_t*)(*env)->GetByteArrayElements(env, message, 0);
|
||||
jsize messageLength = (*env)->GetArrayLength(env, message);
|
||||
|
||||
int result = curve25519_sign(signatureBytes, privateKeyBytes, messageBytes, messageLength, randomBytes);
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, signature, signatureBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, random, randomBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, message, messageBytes, 0);
|
||||
|
||||
if (result == 0) return signature;
|
||||
else (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/AssertionError"), "Signature failed!");
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_whispersystems_libaxolotl_ecc_Curve25519_verifySignature
|
||||
(JNIEnv *env, jclass clazz, jbyteArray publicKey, jbyteArray message, jbyteArray signature)
|
||||
{
|
||||
uint8_t* signatureBytes = (uint8_t*)(*env)->GetByteArrayElements(env, signature, 0);
|
||||
uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0);
|
||||
uint8_t* messageBytes = (uint8_t*)(*env)->GetByteArrayElements(env, message, 0);
|
||||
jsize messageLength = (*env)->GetArrayLength(env, message);
|
||||
|
||||
jboolean result = (curve25519_verify(signatureBytes, publicKeyBytes, messageBytes, messageLength) == 0);
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, signature, signatureBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0);
|
||||
(*env)->ReleaseByteArrayElements(env, message, messageBytes, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
44
libaxolotl/jni/ed25519/additions/compare.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <string.h>
|
||||
#include "compare.h"
|
||||
|
||||
/* Const-time comparison from SUPERCOP, but here it's only used for
|
||||
signature verification, so doesn't need to be const-time. But
|
||||
copied the nacl version anyways. */
|
||||
int crypto_verify_32_ref(const unsigned char *x, const unsigned char *y)
|
||||
{
|
||||
unsigned int differentbits = 0;
|
||||
#define F(i) differentbits |= x[i] ^ y[i];
|
||||
F(0)
|
||||
F(1)
|
||||
F(2)
|
||||
F(3)
|
||||
F(4)
|
||||
F(5)
|
||||
F(6)
|
||||
F(7)
|
||||
F(8)
|
||||
F(9)
|
||||
F(10)
|
||||
F(11)
|
||||
F(12)
|
||||
F(13)
|
||||
F(14)
|
||||
F(15)
|
||||
F(16)
|
||||
F(17)
|
||||
F(18)
|
||||
F(19)
|
||||
F(20)
|
||||
F(21)
|
||||
F(22)
|
||||
F(23)
|
||||
F(24)
|
||||
F(25)
|
||||
F(26)
|
||||
F(27)
|
||||
F(28)
|
||||
F(29)
|
||||
F(30)
|
||||
F(31)
|
||||
return (1 & ((differentbits - 1) >> 8)) - 1;
|
||||
}
|
||||
6
libaxolotl/jni/ed25519/additions/compare.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef __COMPARE_H__
|
||||
#define __COMPARE_H__
|
||||
|
||||
int crypto_verify_32_ref(const unsigned char *b1, const unsigned char *b2);
|
||||
|
||||
#endif
|
||||