Compare commits
449 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca8a480c5 | ||
|
|
349faf18fa | ||
|
|
88ed8f3887 | ||
|
|
6a6f900b7d | ||
|
|
17383ddfe0 | ||
|
|
1b846c0842 | ||
|
|
dbda758841 | ||
|
|
c6b3bb9f13 | ||
|
|
bdeeea2e46 | ||
|
|
427c9a6b21 | ||
|
|
dfda2f733c | ||
|
|
6f600a80b8 | ||
|
|
62181a3d7f | ||
|
|
6c386d3fc9 | ||
|
|
b9e6cff20c | ||
|
|
d65a7f1a25 | ||
|
|
76d0f45b9b | ||
|
|
3a19ac432b | ||
|
|
6ec9ee89a7 | ||
|
|
472179b550 | ||
|
|
f38bd7b187 | ||
|
|
b73143d7a7 | ||
|
|
9a2c372c23 | ||
|
|
b21369b7bc | ||
|
|
5939a1f995 | ||
|
|
e4b73e90e1 | ||
|
|
073d019103 | ||
|
|
761ccf4b3f | ||
|
|
7a023b9fdc | ||
|
|
9f804047f5 | ||
|
|
a625727853 | ||
|
|
e0737451db | ||
|
|
0f572d1e5d | ||
|
|
142f84703b | ||
|
|
b47bd97e9e | ||
|
|
4099303d1f | ||
|
|
71eeb7ca83 | ||
|
|
186a5e4dfc | ||
|
|
0d84ec51a8 | ||
|
|
6c9ed77e5f | ||
|
|
fc3aaed8a8 | ||
|
|
c72cc1b49c | ||
|
|
13227b6aa5 | ||
|
|
cc65fab226 | ||
|
|
dee2e0906e | ||
|
|
e142cb6d26 | ||
|
|
4185006147 | ||
|
|
a4e18c515c | ||
|
|
2011391e65 | ||
|
|
96896bf8f1 | ||
|
|
ac312ff5d5 | ||
|
|
b3552ee347 | ||
|
|
b998c5f86c | ||
|
|
0b8430ff43 | ||
|
|
3e80032c18 | ||
|
|
41fadc3437 | ||
|
|
c1fe585ffb | ||
|
|
078df0f029 | ||
|
|
9f9293cd61 | ||
|
|
4e87513867 | ||
|
|
c05aca19ae | ||
|
|
db2c7fabe1 | ||
|
|
e7eb0ec2b3 | ||
|
|
da75268545 | ||
|
|
49ab1e7bd5 | ||
|
|
b5a2d7882a | ||
|
|
534df06794 | ||
|
|
e31ddf0599 | ||
|
|
ce22e54359 | ||
|
|
7ce7ec56eb | ||
|
|
2b39728ea0 | ||
|
|
2af5c7f02e | ||
|
|
d6e0199e05 | ||
|
|
301ed0b255 | ||
|
|
ac5306be5b | ||
|
|
9de62d0286 | ||
|
|
067b14d551 | ||
|
|
03110c9870 | ||
|
|
a2b5bf54c8 | ||
|
|
5602a3dfc0 | ||
|
|
ed5b3f8679 | ||
|
|
7bf7acb1ff | ||
|
|
ffa5dc549b | ||
|
|
a59feb7ad4 | ||
|
|
753a1c7219 | ||
|
|
004f050741 | ||
|
|
fcde642563 | ||
|
|
ac0a1c0bc1 | ||
|
|
3f93731300 | ||
|
|
00d7b5c284 | ||
|
|
4397b55ceb | ||
|
|
b87abcc2c1 | ||
|
|
79dbf2746f | ||
|
|
a4bd9fae9a | ||
|
|
d4493e700e | ||
|
|
83d65228e9 | ||
|
|
8a2caeef3d | ||
|
|
6280ef4ab8 | ||
|
|
899afa2092 | ||
|
|
7c6b965969 | ||
|
|
5fac189736 | ||
|
|
d3271f548c | ||
|
|
023195dd4b | ||
|
|
299dd86c77 | ||
|
|
f818cfa32b | ||
|
|
fc21d2038f | ||
|
|
8277e4e102 | ||
|
|
242dbef54e | ||
|
|
9ffd84ce05 | ||
|
|
cb37ce0f5f | ||
|
|
7ab739b22b | ||
|
|
224b06f05a | ||
|
|
d36d33f837 | ||
|
|
724dced820 | ||
|
|
b8ca0e57ad | ||
|
|
fe5fc411a7 | ||
|
|
20d43bbfcb | ||
|
|
442f56cdc3 | ||
|
|
35dabaf1e5 | ||
|
|
1aec794a34 | ||
|
|
5bd8d6c69d | ||
|
|
aa26785c00 | ||
|
|
f450b37cfd | ||
|
|
b88e470594 | ||
|
|
f38677794a | ||
|
|
ac4db41435 | ||
|
|
3a9d521ffe | ||
|
|
b1bf33b13b | ||
|
|
352418d2d7 | ||
|
|
fb523985ed | ||
|
|
7ffe6f053c | ||
|
|
ea77191b70 | ||
|
|
e0b882d4d2 | ||
|
|
10a74d6d08 | ||
|
|
f092e85b62 | ||
|
|
8aa0f15740 | ||
|
|
18d4d4de24 | ||
|
|
b3c42dee7e | ||
|
|
5836f35291 | ||
|
|
dbd8a4083c | ||
|
|
0fd52ad1fe | ||
|
|
8031c788d9 | ||
|
|
10a29db93d | ||
|
|
8f3f2e6921 | ||
|
|
65c262acef | ||
|
|
d586893402 | ||
|
|
ba62e018db | ||
|
|
57c17e705f | ||
|
|
2e253fb6a8 | ||
|
|
0c32001fe4 | ||
|
|
48f6c2c526 | ||
|
|
39fd1e8f46 | ||
|
|
810abe0275 | ||
|
|
62816ee51a | ||
|
|
a0599c1639 | ||
|
|
8a4a1b385d | ||
|
|
5def8ba78d | ||
|
|
42da687602 | ||
|
|
e64c067636 | ||
|
|
c89fbabbf3 | ||
|
|
778b8b490c | ||
|
|
bade52d748 | ||
|
|
abc322b075 | ||
|
|
9a31c5961a | ||
|
|
f536e45378 | ||
|
|
18961e6369 | ||
|
|
1505ec8c35 | ||
|
|
d41efdbd1c | ||
|
|
156cb4034e | ||
|
|
d2e188ee62 | ||
|
|
1de16de55b | ||
|
|
ac3b3673b9 | ||
|
|
91bf5532b2 | ||
|
|
b25b95f933 | ||
|
|
a0ed0842a0 | ||
|
|
12845da91a | ||
|
|
3e6e28e688 | ||
|
|
9fed60628d | ||
|
|
e67dafe9c8 | ||
|
|
5e5531805b | ||
|
|
b6822b310e | ||
|
|
3e8d2a557e | ||
|
|
23caf2b14d | ||
|
|
562520a4bb | ||
|
|
75d882e945 | ||
|
|
b39e41c803 | ||
|
|
348352cc71 | ||
|
|
bec5e45605 | ||
|
|
4d8004ab21 | ||
|
|
2e141b2a90 | ||
|
|
ed556fbd3a | ||
|
|
ebf6a2d833 | ||
|
|
25028b7178 | ||
|
|
389128d1a3 | ||
|
|
6e85677c52 | ||
|
|
1443d846c4 | ||
|
|
d7419caa4b | ||
|
|
e277f9f6d1 | ||
|
|
3f0932ff7b | ||
|
|
0a61534e03 | ||
|
|
121b1493cc | ||
|
|
a57c7c3e09 | ||
|
|
b89b986a9e | ||
|
|
1bb882ac96 | ||
|
|
9e7928873b | ||
|
|
21591dc6b1 | ||
|
|
3ec95f7a8e | ||
|
|
33d466a5cc | ||
|
|
e8b947dfde | ||
|
|
60a343120d | ||
|
|
8af74e93a2 | ||
|
|
e650349bb7 | ||
|
|
44a4570870 | ||
|
|
f1572774f2 | ||
|
|
ec18da8aa9 | ||
|
|
199c28dad6 | ||
|
|
4a8a939f64 | ||
|
|
cef203f261 | ||
|
|
d7de5415b5 | ||
|
|
188d11bc23 | ||
|
|
990902845a | ||
|
|
07bb07c342 | ||
|
|
a09e0afbd6 | ||
|
|
bbea017d5a | ||
|
|
426c56c77d | ||
|
|
642334da1f | ||
|
|
b44acd82e6 | ||
|
|
62d0de2ee6 | ||
|
|
3c7e319029 | ||
|
|
715b1ea10d | ||
|
|
6980a32628 | ||
|
|
15f44a78a9 | ||
|
|
6b05f7905b | ||
|
|
9beb51bd77 | ||
|
|
0b62cf372b | ||
|
|
b7badec752 | ||
|
|
a5e5a4c37f | ||
|
|
c448a833f5 | ||
|
|
761049d6f8 | ||
|
|
df12174458 | ||
|
|
6e7566f781 | ||
|
|
30232c1635 | ||
|
|
4a088410ae | ||
|
|
b05967f2c9 | ||
|
|
1d0c9536fc | ||
|
|
3c4ac4b105 | ||
|
|
ff05642f3e | ||
|
|
4e98c350a5 | ||
|
|
e5e5b93884 | ||
|
|
2f6cefca8a | ||
|
|
8149af22ec | ||
|
|
55ee288c36 | ||
|
|
1833e57c38 | ||
|
|
c14945791c | ||
|
|
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 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.ai binary
|
||||
@@ -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,he:iw,id:in
|
||||
|
||||
|
||||
[textsecure-official.master]
|
||||
|
||||
@@ -2,8 +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="79"
|
||||
android:versionName="2.1.7">
|
||||
android:versionCode="105"
|
||||
android:versionName="2.9.2">
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
||||
android:label="Access to TextSecure Secrets"
|
||||
@@ -37,7 +37,7 @@
|
||||
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">
|
||||
@@ -90,7 +90,18 @@
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MmsPreferencesActivity"
|
||||
<activity android:name=".DeviceProvisioningActivity"
|
||||
android:theme="@style/TextSecure.DialogActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="tsdevice"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ShareActivity"
|
||||
@@ -107,19 +118,26 @@
|
||||
|
||||
<activity android:name=".ConversationActivity"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MessageDetailsActivity"
|
||||
android:label="Message Details"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".GroupCreateActivity"
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
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"/>
|
||||
|
||||
@@ -137,14 +155,9 @@
|
||||
android:windowSoftInputMode="stateAlwaysVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ContactSelectionActivity"
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".NewConversationActivity"
|
||||
android:label="@string/AndroidManifest__select_contacts"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".PushContactSelectionActivity"
|
||||
@@ -152,11 +165,6 @@
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".AutoInitiateActivity"
|
||||
android:theme="@style/TextSecure.Light.Dialog"
|
||||
android:label="@string/AndroidManifest__textsecure_detected"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".ViewIdentityActivity"
|
||||
android:label="@string/AndroidManifest__public_identity_key"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
@@ -173,7 +181,6 @@
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
@@ -192,6 +199,16 @@
|
||||
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:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".MediaOverviewActivity"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".DummyActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:enabled="true"
|
||||
@@ -203,13 +220,16 @@
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:finishOnTaskLaunch="true" />
|
||||
|
||||
<service android:enabled="true" android:name=".service.GcmRegistrationService"/>
|
||||
<activity android:name=".PlayServicesProblemActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
|
||||
|
||||
<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=".service.MessageRetrievalService"/>
|
||||
|
||||
<service android:name=".service.QuickResponseService"
|
||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||
@@ -285,6 +305,10 @@
|
||||
android:grantUriPermissions="true"
|
||||
android:authorities="org.thoughtcrime.provider.securesms" />
|
||||
|
||||
<provider android:name=".providers.MmsBodyProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:authorities="org.thoughtcrime.provider.securesms.mms" />
|
||||
|
||||
<receiver android:name=".service.RegistrationNotifier"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
@@ -299,6 +323,17 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".notifications.MessageNotifier$DeleteReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.thoughtcrime.securesms.MessageNotifier.DELETE_REMINDER_ACTION"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
24
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\>
|
||||
@@ -26,19 +30,17 @@ The following steps should help you (re)build TextSecure from the command line.
|
||||
|
||||
./gradlew build
|
||||
|
||||
Re-building native components
|
||||
-----------------------------
|
||||
Visual assets
|
||||
----------------------
|
||||
|
||||
Note: This step is optional; native components are contained as binaries (see [library/libs](library/libs)).
|
||||
Source assets tend to be large binary blobs, which are best stored outside of git repositories. We host ours in a [Pixelapse repository](https://www.pixelapse.com/openwhispersystems/projects/signal-android/). Some source files are SVGs that can be auto-colored and sized using a tool like [android-res-utils](https://github.com/sebkur/android-res-utils).
|
||||
|
||||
1. Ensure that the Android NDK is installed.
|
||||
Sample command for generating our audio placeholder image:
|
||||
|
||||
Execute ndk-build:
|
||||
|
||||
cd library
|
||||
ndk-build
|
||||
|
||||
Afterwards, execute Gradle as above to re-create the APK.
|
||||
```bash
|
||||
pngs_from_svg.py ic_audio.svg /path/to/TextSecure/res/ 150 "#000" 0.54 _light
|
||||
pngs_from_svg.py ic_audio.svg /path/to/TextSecure/res/ 150 "#fff" 1.0 _dark
|
||||
```
|
||||
|
||||
Setting up a development environment
|
||||
------------------------------------
|
||||
|
||||
14
README.md
@@ -1,6 +1,6 @@
|
||||
# TextSecure [](https://travis-ci.org/WhisperSystems/TextSecure)
|
||||
|
||||
TextSecure is a messaging app for easy private communicate with friends.
|
||||
TextSecure is a messaging app for simple private communication with friends.
|
||||
|
||||
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.
|
||||
@@ -14,6 +14,16 @@ We use GitHub for bug tracking. Please search the existing issues for your bug a
|
||||
|
||||
https://github.com/WhisperSystems/TextSecure/issues
|
||||
|
||||
## Joining the Beta
|
||||
Want to live life on the bleeding edge and help out with testing?
|
||||
|
||||
You can subscribe to TextSecure Beta releases in two steps:
|
||||
|
||||
1. Join the [TextSecure Beta Google+ Community](https://plus.google.com/communities/114424213916773497091).
|
||||
1. After you've joined the community, [subscribe to the beta](https://play.google.com/apps/testing/org.thoughtcrime.securesms).
|
||||
|
||||
If you're interesting a life of peace and tranquility, stick with the standard releases.
|
||||
|
||||
## Contributing Translations
|
||||
Interested in helping to translate TextSecure? Contribute here:
|
||||
|
||||
@@ -38,7 +48,7 @@ whispersystems@lists.riseup.net
|
||||
https://lists.riseup.net/www/info/whispersystems
|
||||
|
||||
## Contributing Funds
|
||||
[](https://whispersystems.org/blog/bithub/)
|
||||
[](https://coinbase.com/checkouts/d29fd4c37ca442393e32fdcb95304701)
|
||||
|
||||
You can add funds to BitHub to directly help further development efforts.
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import static android.support.test.espresso.Espresso.*;
|
||||
import static android.support.test.espresso.action.ViewActions.*;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.*;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.*;
|
||||
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
@LargeTest
|
||||
public class RegistrationActivityTest extends RoutedInstrumentationTestCase {
|
||||
private final static String TAG = RegistrationActivityTest.class.getSimpleName();
|
||||
|
||||
public RegistrationActivityTest() {
|
||||
super();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testRegistrationButtons() throws Exception {
|
||||
waitOn(RegistrationActivity.class);
|
||||
onView(withId(R.id.registerButton)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.skipButton)).check(matches(isDisplayed())).perform(click());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation.ActivityMonitor;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
|
||||
public class RoutedInstrumentationTestCase extends ActivityInstrumentationTestCase2<RoutingActivity> {
|
||||
private static final String TAG = RoutedInstrumentationTestCase.class.getSimpleName();
|
||||
|
||||
public RoutedInstrumentationTestCase() {
|
||||
super(RoutingActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
|
||||
super.setUp();
|
||||
clearSharedPrefs();
|
||||
getActivity();
|
||||
}
|
||||
|
||||
protected void clearSharedPrefs() {
|
||||
PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext())
|
||||
.edit().clear().commit();
|
||||
getInstrumentation().getTargetContext().getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
|
||||
.edit().clear().commit();
|
||||
}
|
||||
|
||||
protected static void waitOn(Class<? extends Activity> clazz) {
|
||||
Log.w(TAG, "waiting for " + clazz.getName());
|
||||
new ActivityMonitor(clazz.getName(), null, true).waitForActivityWithTimeout(10000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
public class TextSecureTestCase extends InstrumentationTestCase {
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return getInstrumentation().getContext();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class CanonicalAddressDatabaseTest extends InstrumentationTestCase {
|
||||
public class CanonicalAddressDatabaseTest extends TextSecureTestCase {
|
||||
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";
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ws.com.google.android.mms.pdu.PduPart;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyFloat;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PartDatabaseTest extends TextSecureTestCase {
|
||||
private static final long PART_ID = 1L;
|
||||
|
||||
private PartDatabase database;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
database = spy(DatabaseFactory.getPartDatabase(getInstrumentation().getTargetContext()));
|
||||
}
|
||||
|
||||
public void testTaskNotRunWhenThumbnailExists() throws Exception {
|
||||
when(database.getPart(eq(PART_ID))).thenReturn(getPduPartSkeleton("x/x"));
|
||||
doReturn(mock(InputStream.class)).when(database).getDataStream(any(MasterSecret.class), anyLong(), eq("thumbnail"));
|
||||
|
||||
database.getThumbnailStream(null, PART_ID);
|
||||
|
||||
verify(database, never()).updatePartThumbnail(any(MasterSecret.class), anyLong(), any(PduPart.class), any(InputStream.class), anyFloat());
|
||||
}
|
||||
|
||||
public void testTaskRunWhenThumbnailMissing() throws Exception {
|
||||
when(database.getPart(eq(PART_ID))).thenReturn(getPduPartSkeleton("image/png"));
|
||||
doReturn(null).when(database).getDataStream(any(MasterSecret.class), anyLong(), eq("thumbnail"));
|
||||
doNothing().when(database).updatePartThumbnail(any(MasterSecret.class), anyLong(), any(PduPart.class), any(InputStream.class), anyFloat());
|
||||
|
||||
try {
|
||||
database.new ThumbnailFetchCallable(mock(MasterSecret.class), PART_ID).call();
|
||||
throw new AssertionError("didn't try to generate thumbnail");
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// success
|
||||
}
|
||||
}
|
||||
|
||||
private PduPart getPduPartSkeleton(String contentType) {
|
||||
PduPart part = new PduPart();
|
||||
part.setContentType(contentType.getBytes());
|
||||
part.setDataUri(Uri.EMPTY);
|
||||
return part;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
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 TextSecureTestCase {
|
||||
|
||||
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,100 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.api.TextSecureMessageSender;
|
||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||
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 TextSecureTestCase {
|
||||
|
||||
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<TextSecureAddress> captor = ArgumentCaptor.forClass(TextSecureAddress.class);
|
||||
verify(textSecureMessageSender).sendDeliveryReceipt(captor.capture(), eq(timestamp));
|
||||
|
||||
assertTrue(captor.getValue().getRelay().get().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(TextSecureAddress.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(TextSecureAddress.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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import android.test.AndroidTestCase;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import org.thoughtcrime.securesms.TextSecureTestCase;
|
||||
import org.whispersystems.textsecure.api.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
|
||||
|
||||
public class PhoneNumberFormatterTest extends AndroidTestCase {
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class PhoneNumberFormatterTest extends TextSecureTestCase {
|
||||
private static final String LOCAL_NUMBER = "+15555555555";
|
||||
|
||||
public void testFormatNumberE164() throws Exception, InvalidNumberException {
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
public class UtilTest extends AndroidTestCase {
|
||||
|
||||
}
|
||||
1884
apntool/apnlists/cyanogenmod.xml
Normal file
@@ -562,6 +562,7 @@
|
||||
<apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="9" server="*" />
|
||||
<apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="3" server="*" />
|
||||
<apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="1" server="*" />
|
||||
<apn mcc="310" mnc="000" carrier="Verizon CDMA HRPD" mmsc="http://mms.vzwreseller.com/servlets/mms" type="default,mms,hipri,dun,supl" authtype="3" mvno_type="spn" mvno_match_data="Tracfone" />
|
||||
<apn mcc="310" mnc="4" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE" />
|
||||
<apn mcc="310" mnc="4" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE" />
|
||||
<apn mcc="310" mnc="4" carrier="Verizon CDMA HRPD" apn="CdmaNai" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,hipri,dun,stdhipri,supl" protocol="IPV4V6" bearer="6" carrier_enabled="FALSE" />
|
||||
@@ -1717,8 +1718,6 @@
|
||||
<apn mcc="434" mnc="4" carrier="Beeline-UZB MMS" apn="mms.beeline.uz" mmsc="http://mms" mmsproxy="172.30.30.166" mmsport="8080" type="mms" user="beeline" password="beeline" authtype="1" />
|
||||
<apn mcc="434" mnc="5" carrier="UCELL MMS" apn="mms" mmsc="http://mmsc:8002/" mmsproxy="10.64.164.10" mmsport="8080" type="mms" />
|
||||
<apn mcc="434" mnc="07" carrier="MTS-UZB MMS" apn="mms.mts.uz" mmsc="http://mmsc/was" mmsproxy="10.10.0.10" mmsport="8080" type="mms" />
|
||||
<apn mcc="440" mnc="20" carrier="Application" apn="plus.acs.jp" mmsc="http://mms-s" mmsproxy="andmms.plusacs.ne.jp" mmsport="8080" type="default,mms,supl" user="plusw6q9tattkmpk" password="msfbbam83bsdetxb" authtype="2" />
|
||||
<apn mcc="440" mnc="20" carrier="Application" apn="andoworld.softbank.ne.jp" mmsc="http://mms/" mmsproxy="andmms.softbank.ne.jp" mmsport="8080" type="default,mms,supl,hpri" authtype="0" />
|
||||
<apn mcc="450" mnc="5" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" authtype="0" server="*" />
|
||||
<apn mcc="450" mnc="5" carrier="SKT-Home" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms" authtype="0" server="*" />
|
||||
<apn mcc="450" mnc="05" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="ia,default,mms,supl,fota,cbs" server="*" />
|
||||
@@ -1,19 +1,31 @@
|
||||
import sys
|
||||
import re
|
||||
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 = 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.1')
|
||||
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()
|
||||
|
||||
|
||||
def normalized(target):
|
||||
o2_typo = re.compile(r"02\.co\.uk")
|
||||
port_typo = re.compile(r"(\d+\.\d+\.\d+\.\d+)\.(\d+)")
|
||||
leading_zeros = re.compile(r"(/|\.|^)0+(\d+)")
|
||||
subbed = o2_typo.sub(r'o2.co.uk', target)
|
||||
subbed = port_typo.sub(r'\1:\2', subbed)
|
||||
subbed = leading_zeros.sub(r'\1\2', subbed)
|
||||
return subbed
|
||||
|
||||
try:
|
||||
connection = sqlite3.connect(args.output)
|
||||
cursor = connection.cursor()
|
||||
@@ -28,23 +40,41 @@ try:
|
||||
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,
|
||||
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
|
||||
pbar = None
|
||||
if not args.quiet:
|
||||
pbar = ProgressBar(widgets=['Processed: ', Counter(), ' apns (', Timer(), ')'], maxval=len(list(root))).start()
|
||||
|
||||
count = 0
|
||||
for apn in root.iter("apn"):
|
||||
if apn.get("mmsc") is None:
|
||||
continue
|
||||
sqlvars = ["?" for x in apn.attrib.keys()] + ["?"]
|
||||
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + ["%s%s" % (apn.get("mcc"), apn.get("mnc"))]
|
||||
mccmnc = "%s%s" % (apn.get("mcc"), apn.get("mnc"))
|
||||
normalized_mmsc = normalized(apn.get("mmsc"))
|
||||
if normalized_mmsc != apn.get("mmsc"):
|
||||
print("normalize MMSC: %s => %s" % (apn.get("mmsc"), normalized_mmsc))
|
||||
apn.set("mmsc", normalized_mmsc)
|
||||
|
||||
if not apn.get("mmsproxy") is None:
|
||||
normalized_mmsproxy = normalized(apn.get("mmsproxy"))
|
||||
if normalized_mmsproxy != apn.get("mmsproxy"):
|
||||
print("normalize proxy: %s => %s" % (apn.get("mmsproxy"), normalized_mmsproxy))
|
||||
apn.set("mmsproxy", normalized_mmsproxy)
|
||||
|
||||
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + [mccmnc]
|
||||
keys = apn.attrib.keys() + ["mccmnc"]
|
||||
|
||||
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
|
||||
cursor.execute(statement, values)
|
||||
cursor.execute("SELECT 1 FROM apns WHERE mccmnc = ? AND apn = ?", [mccmnc, apn.get("apn")])
|
||||
if cursor.fetchone() is None:
|
||||
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
|
||||
cursor.execute(statement, values)
|
||||
|
||||
count += 1
|
||||
if not args.quiet:
|
||||
|
||||
31036
artwork/icon.ai
|
Before Width: | Height: | Size: 496 KiB |
|
Before Width: | Height: | Size: 93 KiB |
BIN
artwork/logo-512.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 323 KiB |
|
Before Width: | Height: | Size: 547 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 477 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 363 KiB After Width: | Height: | Size: 278 KiB |
BIN
assets/fonts/Roboto-Light.ttf
Normal file
185
build.gradle
@@ -1,9 +1,11 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo1.maven.org/maven2"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.2'
|
||||
classpath 'com.android.tools.build:gradle:1.0.1'
|
||||
classpath files('libs/gradle-witness.jar')
|
||||
}
|
||||
}
|
||||
@@ -12,89 +14,192 @@ apply plugin: 'com.android.application'
|
||||
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/gson/releases/"
|
||||
url "https://raw.github.com/whispersystems/maven/master/preferencefragment/releases/"
|
||||
}
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/smil/releases/"
|
||||
}
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/shortcutbadger/releases/"
|
||||
}
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
|
||||
compile 'com.android.support:support-v4:20.0.0'
|
||||
compile 'me.leolin:ShortcutBadger:1.0.2-WS2'
|
||||
compile 'se.emilsjolander:stickylistheaders:2.2.0'
|
||||
compile 'com.google.android.gms:play-services:5.0.89'
|
||||
compile 'com.google.android.gms:play-services-base:6.5.87'
|
||||
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.makeramen:roundedimageview:1.5.0'
|
||||
compile ('com.afollestad:material-dialogs:0.6.4.7') {
|
||||
exclude module: 'appcompat-v7'
|
||||
exclude module: 'recyclerview-v7'
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
|
||||
androidTestCompile 'com.squareup:fest-android:1.0.8'
|
||||
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
|
||||
compile 'com.android.support:appcompat-v7:21.0.3'
|
||||
compile 'com.android.support:recyclerview-v7:21.0.3'
|
||||
compile 'com.melnykov:floatingactionbutton:1.1.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'
|
||||
compile ("com.doomonafireball.betterpickers:library:1.5.3") {
|
||||
exclude group: 'com.android.support', module: 'support-v4'
|
||||
}
|
||||
compile 'com.madgag.spongycastle:prov:1.51.0.0'
|
||||
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
|
||||
|
||||
compile project(':library')
|
||||
compile 'org.whispersystems:jobmanager:0.11.0'
|
||||
compile 'org.whispersystems:libpastelog:1.0.6'
|
||||
compile 'org.whispersystems:textsecure-android:1.2.4'
|
||||
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
|
||||
androidTestCompile ('org.assertj:assertj-core:1.7.1') {
|
||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||
}
|
||||
androidTestCompile ('com.squareup.assertj:assertj-android:1.0.0') {
|
||||
exclude group: 'org.hamcrest', module: 'hamcrest-core'
|
||||
}
|
||||
androidTestCompile ('com.android.support.test.espresso:espresso-core:2.0') {
|
||||
exclude group: 'javax.inject'
|
||||
}
|
||||
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.actionbarsherlock:actionbarsherlock:5ab04d74101f70024b222e3ff9c87bee151ec43331b4a2134b6cc08cf8565819',
|
||||
'com.android.support:support-v4:81f2b1c2c94efd5a4ec7fcd97b6cdcd00e87a933905c5c86103c7319eb024572',
|
||||
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
|
||||
'com.google.android.gms:play-services:38f326e525830f1d70f60f594ceafcbdf5b312287ddbecd338fd1ed7958a4b1e',
|
||||
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
|
||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
||||
'com.google.protobuf:protobuf-java:ad9769a22989e688a46af4d3accc348cc501ced22118033230542bc916e33f0b',
|
||||
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
|
||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
||||
'me.leolin:ShortcutBadger:027977c718035e5997035e04e05152d6c72d94df645e8b7099a274ada722bd14',
|
||||
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
|
||||
'com.google.android.gms:play-services-base:832cb6b3130e871db6a412c4ab585656dbcc5e7948101f190186757785703f75',
|
||||
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
|
||||
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
|
||||
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
|
||||
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
|
||||
'com.makeramen:roundedimageview:7dda2e78c406760e5c356ccce59b0df46b5b171cf18abb891998594405021548',
|
||||
'com.afollestad:material-dialogs:6ed57c1c479219f8ae929c310fc171dbfcbcee8326a6dcd50a91959d69eccdf0',
|
||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
||||
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
|
||||
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
|
||||
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
|
||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
|
||||
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
|
||||
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
|
||||
'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d',
|
||||
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
|
||||
'org.whispersystems:textsecure-android:c867b4e1f9cf430aa15c73841833469f71af9c6149becea447a72b96de214398',
|
||||
'com.android.support:support-annotations:fdee2354787ef66b268e75958de3f7f6c4f8f325510a6dac9f49c929f83a63de',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
|
||||
'org.whispersystems:textsecure-java:141132e1c917759bdc285f80ed9e9784be0dc99ec6872f738439505955832906',
|
||||
'org.whispersystems:axolotl-android:40d3db5004a84749a73f68d2f0d01b2ae35a73c54df96d8c6c6723b96efb6fc0',
|
||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||
'com.squareup.okhttp:okhttp:89b7f63e2e5b6c410266abc14f50fe52ea8d2d8a57260829e499b1cd9f0e61af',
|
||||
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
|
||||
'org.whispersystems:axolotl-java:6daee739b89d8d7101de6d98f77132fee48495c6ea647d880e77def842f999ea',
|
||||
'org.whispersystems:curve25519-android:3c29a4131a69b0d16baaa3d707678deb39602c3a3ffd75805ce7f9db252e5d0d',
|
||||
'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978',
|
||||
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
|
||||
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
|
||||
'org.whispersystems:curve25519-java:9ccef8f5aba05d9942336f023c589d6278b4f9135bdc34a7bade1f4e7ad65fa3',
|
||||
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
|
||||
]
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.1.0'
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion '22.0.1'
|
||||
|
||||
dexOptions {
|
||||
javaMaxHeapSize "4g"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 19
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L"
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
androidTest {
|
||||
java.srcDirs = ['androidTest']
|
||||
resources.srcDirs = ['androidTest']
|
||||
aidl.srcDirs = ['androidTest']
|
||||
renderscript.srcDirs = ['androidTest']
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'LICENSE.txt'
|
||||
exclude 'LICENSE'
|
||||
exclude 'NOTICE'
|
||||
exclude 'asm-license.txt'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled true
|
||||
proguardFiles 'proguard.cfg'
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles 'proguard.cfg'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
androidTest {
|
||||
java.srcDirs = ['androidTest/java']
|
||||
resources.srcDirs = ['androidTest/java']
|
||||
aidl.srcDirs = ['androidTest/java']
|
||||
renderscript.srcDirs = ['androidTest/java']
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.equals("lint")) {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
def Properties props = new Properties()
|
||||
def propFile = new File('signing.properties')
|
||||
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Mon Jun 09 23:26:49 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.12-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.whispersystems.textsecure"
|
||||
android:versionCode="1"
|
||||
android:versionName="0.1">
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="16"/>
|
||||
<application />
|
||||
</manifest>
|
||||
@@ -1,58 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.2'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
apply plugin: 'maven'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://raw.github.com/whispersystems/maven/master/gson/releases/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.protobuf:protobuf-java:2.4.1'
|
||||
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
||||
compile 'org.whispersystems:gson:2.2.4'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion '19.1.0'
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
resources.srcDirs = ['src']
|
||||
aidl.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
version '0.1'
|
||||
group 'org.whispersystems.textsecure'
|
||||
archivesBaseName = 'textsecure-library'
|
||||
|
||||
uploadArchives {
|
||||
repositories {
|
||||
mavenDeployer {
|
||||
repository(url: mavenLocal().getUrl())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="library" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties"/>
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties"/>
|
||||
|
||||
<!-- if sdk.dir was not set from one of the property file, then
|
||||
get it from the ANDROID_HOME env var.
|
||||
This must be done before we load project.properties since
|
||||
the proguard config can use sdk.dir -->
|
||||
<property environment="env"/>
|
||||
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
|
||||
<isset property="env.ANDROID_HOME"/>
|
||||
</condition>
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties"/>
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true"/>
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: 1 -->
|
||||
<import file="${sdk.dir}/tools/ant/build.xml"/>
|
||||
|
||||
</project>
|
||||
@@ -1,17 +0,0 @@
|
||||
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
|
||||
LOCAL_SRC_FILES := curve25519-donna-jni.c
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libcurve25519-donna
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -1 +0,0 @@
|
||||
APP_ABI := armeabi armeabi-v7a x86
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include "curve25519-donna.h"
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePrivateKey
|
||||
(JNIEnv *env, jclass clazz, jbyteArray random, jboolean ephemeral)
|
||||
{
|
||||
uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
|
||||
|
||||
privateKey[0] &= 248;
|
||||
|
||||
if (ephemeral) {
|
||||
privateKey[0] |= 1;
|
||||
}
|
||||
|
||||
privateKey[31] &= 127;
|
||||
privateKey[31] |= 64;
|
||||
|
||||
(*env)->ReleaseByteArrayElements(env, random, privateKey, 0);
|
||||
|
||||
return random;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_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_textsecure_crypto_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;
|
||||
}
|
||||
@@ -1,731 +0,0 @@
|
||||
/* Copyright 2008, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* curve25519-donna: Curve25519 elliptic curve, public key function
|
||||
*
|
||||
* http://code.google.com/p/curve25519-donna/
|
||||
*
|
||||
* Adam Langley <agl@imperialviolet.org>
|
||||
*
|
||||
* Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
|
||||
*
|
||||
* More information about curve25519 can be found here
|
||||
* http://cr.yp.to/ecdh.html
|
||||
*
|
||||
* djb's sample implementation of curve25519 is written in a special assembly
|
||||
* language called qhasm and uses the floating point registers.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define inline __inline
|
||||
#endif
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t limb;
|
||||
|
||||
/* Field element representation:
|
||||
*
|
||||
* Field elements are written as an array of signed, 64-bit limbs, least
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* 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]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Find the difference of two numbers: output = in - output
|
||||
* (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]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Multiply a number by a scalar: output = in * scalar */
|
||||
static void fscalar_product(limb *output, const limb *in, const limb scalar) {
|
||||
unsigned i;
|
||||
for (i = 0; i < 10; ++i) {
|
||||
output[i] = in[i] * scalar;
|
||||
}
|
||||
}
|
||||
|
||||
/* Multiply two numbers: output = in2 * in
|
||||
*
|
||||
* output must be distinct to both inputs. The inputs are reduced coefficient
|
||||
* form, the output is not.
|
||||
*/
|
||||
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]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[0]);
|
||||
output[2] = 2 * ((limb) ((s32) in2[1])) * ((s32) in[1]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[0]);
|
||||
output[3] = ((limb) ((s32) in2[1])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[1]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[0]);
|
||||
output[4] = ((limb) ((s32) in2[2])) * ((s32) in[2]) +
|
||||
2 * (((limb) ((s32) in2[1])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[1])) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[0]);
|
||||
output[5] = ((limb) ((s32) in2[2])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[1]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[0]);
|
||||
output[6] = 2 * (((limb) ((s32) in2[3])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[1])) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[0]);
|
||||
output[7] = ((limb) ((s32) in2[3])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[1]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[0]);
|
||||
output[8] = ((limb) ((s32) in2[4])) * ((s32) in[4]) +
|
||||
2 * (((limb) ((s32) in2[3])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[1])) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[0]);
|
||||
output[9] = ((limb) ((s32) in2[4])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[1]) +
|
||||
((limb) ((s32) in2[0])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[0]);
|
||||
output[10] = 2 * (((limb) ((s32) in2[5])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[1])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[1])) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[2]);
|
||||
output[11] = ((limb) ((s32) in2[5])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in2[2])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[2]);
|
||||
output[12] = ((limb) ((s32) in2[6])) * ((s32) in[6]) +
|
||||
2 * (((limb) ((s32) in2[5])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[3])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[3])) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[4]);
|
||||
output[13] = ((limb) ((s32) in2[6])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[7])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in2[4])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[4]);
|
||||
output[14] = 2 * (((limb) ((s32) in2[7])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[5])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[5])) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[6]);
|
||||
output[15] = ((limb) ((s32) in2[7])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in2[8])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in2[6])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[6]);
|
||||
output[16] = ((limb) ((s32) in2[8])) * ((s32) in[8]) +
|
||||
2 * (((limb) ((s32) in2[7])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[7]));
|
||||
output[17] = ((limb) ((s32) in2[8])) * ((s32) in[9]) +
|
||||
((limb) ((s32) in2[9])) * ((s32) in[8]);
|
||||
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. */
|
||||
static void freduce_degree(limb *output) {
|
||||
/* Each of these shifts and adds ends up multiplying the value by 19. */
|
||||
output[8] += output[18] << 4;
|
||||
output[8] += output[18] << 1;
|
||||
output[8] += output[18];
|
||||
output[7] += output[17] << 4;
|
||||
output[7] += output[17] << 1;
|
||||
output[7] += output[17];
|
||||
output[6] += output[16] << 4;
|
||||
output[6] += output[16] << 1;
|
||||
output[6] += output[16];
|
||||
output[5] += output[15] << 4;
|
||||
output[5] += output[15] << 1;
|
||||
output[5] += output[15];
|
||||
output[4] += output[14] << 4;
|
||||
output[4] += output[14] << 1;
|
||||
output[4] += output[14];
|
||||
output[3] += output[13] << 4;
|
||||
output[3] += output[13] << 1;
|
||||
output[3] += output[13];
|
||||
output[2] += output[12] << 4;
|
||||
output[2] += output[12] << 1;
|
||||
output[2] += output[12];
|
||||
output[1] += output[11] << 4;
|
||||
output[1] += output[11] << 1;
|
||||
output[1] += output[11];
|
||||
output[0] += output[10] << 4;
|
||||
output[0] += output[10] << 1;
|
||||
output[0] += output[10];
|
||||
}
|
||||
|
||||
#if (-1 & 3) != 3
|
||||
#error "This code only works on a two's complement system"
|
||||
#endif
|
||||
|
||||
/* return v / 2^26, using only shifts and adds. */
|
||||
static inline limb
|
||||
div_by_2_26(const limb v)
|
||||
{
|
||||
/* 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;
|
||||
/* Set to 0x3ffffff if v was negative; else set to 0. */
|
||||
const int32_t roundoff = ((uint32_t) sign) >> 6;
|
||||
/* Should return v / (1<<26) */
|
||||
return (v + roundoff) >> 26;
|
||||
}
|
||||
|
||||
/* return v / (2^25), using only shifts and adds. */
|
||||
static inline limb
|
||||
div_by_2_25(const limb v)
|
||||
{
|
||||
/* 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;
|
||||
/* Set to 0x1ffffff if v was negative; else set to 0. */
|
||||
const int32_t roundoff = ((uint32_t) sign) >> 7;
|
||||
/* Should return v / (1<<25) */
|
||||
return (v + roundoff) >> 25;
|
||||
}
|
||||
|
||||
static inline s32
|
||||
div_s32_by_2_25(const s32 v)
|
||||
{
|
||||
const s32 roundoff = ((uint32_t)(v >> 31)) >> 7;
|
||||
return (v + roundoff) >> 25;
|
||||
}
|
||||
|
||||
/* Reduce all coefficients of the short form input so that |x| < 2^26.
|
||||
*
|
||||
* On entry: |output[i]| < 2^62
|
||||
*/
|
||||
static void freduce_coefficients(limb *output) {
|
||||
unsigned i;
|
||||
|
||||
output[10] = 0;
|
||||
|
||||
for (i = 0; i < 10; i += 2) {
|
||||
limb over = div_by_2_26(output[i]);
|
||||
output[i] -= over << 26;
|
||||
output[i+1] += over;
|
||||
|
||||
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. */
|
||||
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 */
|
||||
{
|
||||
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. */
|
||||
}
|
||||
|
||||
/* A helpful wrapper around fproduct: output = in * in2.
|
||||
*
|
||||
* output must be distinct to both inputs. The output is reduced degree and
|
||||
* reduced coefficient.
|
||||
*/
|
||||
static void
|
||||
fmul(limb *output, const limb *in, const limb *in2) {
|
||||
limb t[19];
|
||||
fproduct(t, in, in2);
|
||||
freduce_degree(t);
|
||||
freduce_coefficients(t);
|
||||
memcpy(output, t, sizeof(limb) * 10);
|
||||
}
|
||||
|
||||
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]);
|
||||
output[2] = 2 * (((limb) ((s32) in[1])) * ((s32) in[1]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[2]));
|
||||
output[3] = 2 * (((limb) ((s32) in[1])) * ((s32) in[2]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[3]));
|
||||
output[4] = ((limb) ((s32) in[2])) * ((s32) in[2]) +
|
||||
4 * ((limb) ((s32) in[1])) * ((s32) in[3]) +
|
||||
2 * ((limb) ((s32) in[0])) * ((s32) in[4]);
|
||||
output[5] = 2 * (((limb) ((s32) in[2])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in[1])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[5]));
|
||||
output[6] = 2 * (((limb) ((s32) in[3])) * ((s32) in[3]) +
|
||||
((limb) ((s32) in[2])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[6]) +
|
||||
2 * ((limb) ((s32) in[1])) * ((s32) in[5]));
|
||||
output[7] = 2 * (((limb) ((s32) in[3])) * ((s32) in[4]) +
|
||||
((limb) ((s32) in[2])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in[1])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[7]));
|
||||
output[8] = ((limb) ((s32) in[4])) * ((s32) in[4]) +
|
||||
2 * (((limb) ((s32) in[2])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[8]) +
|
||||
2 * (((limb) ((s32) in[1])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[3])) * ((s32) in[5])));
|
||||
output[9] = 2 * (((limb) ((s32) in[4])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in[3])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in[2])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[1])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in[0])) * ((s32) in[9]));
|
||||
output[10] = 2 * (((limb) ((s32) in[5])) * ((s32) in[5]) +
|
||||
((limb) ((s32) in[4])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in[2])) * ((s32) in[8]) +
|
||||
2 * (((limb) ((s32) in[3])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[1])) * ((s32) in[9])));
|
||||
output[11] = 2 * (((limb) ((s32) in[5])) * ((s32) in[6]) +
|
||||
((limb) ((s32) in[4])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[3])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in[2])) * ((s32) in[9]));
|
||||
output[12] = ((limb) ((s32) in[6])) * ((s32) in[6]) +
|
||||
2 * (((limb) ((s32) in[4])) * ((s32) in[8]) +
|
||||
2 * (((limb) ((s32) in[5])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[3])) * ((s32) in[9])));
|
||||
output[13] = 2 * (((limb) ((s32) in[6])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[5])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in[4])) * ((s32) in[9]));
|
||||
output[14] = 2 * (((limb) ((s32) in[7])) * ((s32) in[7]) +
|
||||
((limb) ((s32) in[6])) * ((s32) in[8]) +
|
||||
2 * ((limb) ((s32) in[5])) * ((s32) in[9]));
|
||||
output[15] = 2 * (((limb) ((s32) in[7])) * ((s32) in[8]) +
|
||||
((limb) ((s32) in[6])) * ((s32) in[9]));
|
||||
output[16] = ((limb) ((s32) in[8])) * ((s32) in[8]) +
|
||||
4 * ((limb) ((s32) in[7])) * ((s32) in[9]);
|
||||
output[17] = 2 * ((limb) ((s32) in[8])) * ((s32) in[9]);
|
||||
output[18] = 2 * ((limb) ((s32) in[9])) * ((s32) in[9]);
|
||||
}
|
||||
|
||||
static void
|
||||
fsquare(limb *output, const limb *in) {
|
||||
limb t[19];
|
||||
fsquare_inner(t, in);
|
||||
freduce_degree(t);
|
||||
freduce_coefficients(t);
|
||||
memcpy(output, t, sizeof(limb) * 10);
|
||||
}
|
||||
|
||||
/* Take a little-endian, 32-byte number and expand it into polynomial form */
|
||||
static void
|
||||
fexpand(limb *output, const u8 *input) {
|
||||
#define F(n,start,shift,mask) \
|
||||
output[n] = ((((limb) input[start + 0]) | \
|
||||
((limb) input[start + 1]) << 8 | \
|
||||
((limb) input[start + 2]) << 16 | \
|
||||
((limb) input[start + 3]) << 24) >> shift) & mask;
|
||||
F(0, 0, 0, 0x3ffffff);
|
||||
F(1, 3, 2, 0x1ffffff);
|
||||
F(2, 6, 3, 0x3ffffff);
|
||||
F(3, 9, 5, 0x1ffffff);
|
||||
F(4, 12, 6, 0x3ffffff);
|
||||
F(5, 16, 0, 0x1ffffff);
|
||||
F(6, 19, 1, 0x3ffffff);
|
||||
F(7, 22, 3, 0x1ffffff);
|
||||
F(8, 25, 4, 0x3ffffff);
|
||||
F(9, 28, 6, 0x3ffffff);
|
||||
#undef F
|
||||
}
|
||||
|
||||
#if (-32 >> 1) != -16
|
||||
#error "This code only works when >> does sign-extension on negative numbers"
|
||||
#endif
|
||||
|
||||
/* Take a fully reduced polynomial form number and contract it into a
|
||||
* little-endian, 32-byte array
|
||||
*/
|
||||
static void
|
||||
fcontract(u8 *output, limb *input) {
|
||||
int i;
|
||||
int j;
|
||||
|
||||
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;
|
||||
} 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 = (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);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
input[1] <<= 2;
|
||||
input[2] <<= 3;
|
||||
input[3] <<= 5;
|
||||
input[4] <<= 6;
|
||||
input[6] <<= 1;
|
||||
input[7] <<= 3;
|
||||
input[8] <<= 4;
|
||||
input[9] <<= 6;
|
||||
#define F(i, s) \
|
||||
output[s+0] |= input[i] & 0xff; \
|
||||
output[s+1] = (input[i] >> 8) & 0xff; \
|
||||
output[s+2] = (input[i] >> 16) & 0xff; \
|
||||
output[s+3] = (input[i] >> 24) & 0xff;
|
||||
output[0] = 0;
|
||||
output[16] = 0;
|
||||
F(0,0);
|
||||
F(1,3);
|
||||
F(2,6);
|
||||
F(3,9);
|
||||
F(4,12);
|
||||
F(5,16);
|
||||
F(6,19);
|
||||
F(7,22);
|
||||
F(8,25);
|
||||
F(9,28);
|
||||
#undef F
|
||||
}
|
||||
|
||||
/* Input: Q, Q', Q-Q'
|
||||
* Output: 2Q, Q+Q'
|
||||
*
|
||||
* x2 z3: long form
|
||||
* x3 z3: long form
|
||||
* x z: short form, destroyed
|
||||
* xprime zprime: short form, destroyed
|
||||
* qmqp: short form, preserved
|
||||
*/
|
||||
static void fmonty(limb *x2, limb *z2, /* output 2Q */
|
||||
limb *x3, limb *z3, /* output Q + Q' */
|
||||
limb *x, limb *z, /* input Q */
|
||||
limb *xprime, limb *zprime, /* input Q' */
|
||||
const limb *qmqp /* input Q - Q' */) {
|
||||
limb origx[10], origxprime[10], zzz[19], xx[19], zz[19], xxprime[19],
|
||||
zzprime[19], zzzprime[19], xxxprime[19];
|
||||
|
||||
memcpy(origx, x, 10 * sizeof(limb));
|
||||
fsum(x, z);
|
||||
fdifference(z, origx); // does x - z
|
||||
|
||||
memcpy(origxprime, xprime, sizeof(limb) * 10);
|
||||
fsum(xprime, zprime);
|
||||
fdifference(zprime, origxprime);
|
||||
fproduct(xxprime, xprime, z);
|
||||
fproduct(zzprime, x, zprime);
|
||||
freduce_degree(xxprime);
|
||||
freduce_coefficients(xxprime);
|
||||
freduce_degree(zzprime);
|
||||
freduce_coefficients(zzprime);
|
||||
memcpy(origxprime, xxprime, sizeof(limb) * 10);
|
||||
fsum(xxprime, zzprime);
|
||||
fdifference(zzprime, origxprime);
|
||||
fsquare(xxxprime, xxprime);
|
||||
fsquare(zzzprime, zzprime);
|
||||
fproduct(zzprime, zzzprime, qmqp);
|
||||
freduce_degree(zzprime);
|
||||
freduce_coefficients(zzprime);
|
||||
memcpy(x3, xxxprime, sizeof(limb) * 10);
|
||||
memcpy(z3, zzprime, sizeof(limb) * 10);
|
||||
|
||||
fsquare(xx, x);
|
||||
fsquare(zz, z);
|
||||
fproduct(x2, xx, zz);
|
||||
freduce_degree(x2);
|
||||
freduce_coefficients(x2);
|
||||
fdifference(zz, xx); // does zz = xx - zz
|
||||
memset(zzz + 10, 0, sizeof(limb) * 9);
|
||||
fscalar_product(zzz, zz, 121665);
|
||||
/* No need to call freduce_degree here:
|
||||
fscalar_product doesn't increase the degree of its input. */
|
||||
freduce_coefficients(zzz);
|
||||
fsum(zzz, xx);
|
||||
fproduct(z2, zz, zzz);
|
||||
freduce_degree(z2);
|
||||
freduce_coefficients(z2);
|
||||
}
|
||||
|
||||
/* Conditionally swap two reduced-form limb arrays if 'iswap' is 1, but leave
|
||||
* them unchanged if 'iswap' is 0. Runs in data-invariant time to avoid
|
||||
* side-channel attacks.
|
||||
*
|
||||
* NOTE that this function requires that 'iswap' be 1 or 0; other values give
|
||||
* 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.
|
||||
*/
|
||||
static void
|
||||
swap_conditional(limb a[19], limb b[19], limb iswap) {
|
||||
unsigned i;
|
||||
const s32 swap = (s32) -iswap;
|
||||
|
||||
for (i = 0; i < 10; ++i) {
|
||||
const s32 x = swap & ( ((s32)a[i]) ^ ((s32)b[i]) );
|
||||
a[i] = ((s32)a[i]) ^ x;
|
||||
b[i] = ((s32)b[i]) ^ x;
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculates nQ where Q is the x-coordinate of a point on the curve
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
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};
|
||||
limb *nqpqx = a, *nqpqz = b, *nqx = c, *nqz = d, *t;
|
||||
limb e[19] = {0}, f[19] = {1}, g[19] = {0}, h[19] = {1};
|
||||
limb *nqpqx2 = e, *nqpqz2 = f, *nqx2 = g, *nqz2 = h;
|
||||
|
||||
unsigned i, j;
|
||||
|
||||
memcpy(nqpqx, q, sizeof(limb) * 10);
|
||||
|
||||
for (i = 0; i < 32; ++i) {
|
||||
u8 byte = n[31 - i];
|
||||
for (j = 0; j < 8; ++j) {
|
||||
const limb bit = byte >> 7;
|
||||
|
||||
swap_conditional(nqx, nqpqx, bit);
|
||||
swap_conditional(nqz, nqpqz, bit);
|
||||
fmonty(nqx2, nqz2,
|
||||
nqpqx2, nqpqz2,
|
||||
nqx, nqz,
|
||||
nqpqx, nqpqz,
|
||||
q);
|
||||
swap_conditional(nqx2, nqpqx2, bit);
|
||||
swap_conditional(nqz2, nqpqz2, bit);
|
||||
|
||||
t = nqx;
|
||||
nqx = nqx2;
|
||||
nqx2 = t;
|
||||
t = nqz;
|
||||
nqz = nqz2;
|
||||
nqz2 = t;
|
||||
t = nqpqx;
|
||||
nqpqx = nqpqx2;
|
||||
nqpqx2 = t;
|
||||
t = nqpqz;
|
||||
nqpqz = nqpqz2;
|
||||
nqpqz2 = t;
|
||||
|
||||
byte <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(resultx, nqx, sizeof(limb) * 10);
|
||||
memcpy(resultz, nqz, sizeof(limb) * 10);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Shamelessly copied from djb's code
|
||||
// -----------------------------------------------------------------------------
|
||||
static void
|
||||
crecip(limb *out, const limb *z) {
|
||||
limb z2[10];
|
||||
limb z9[10];
|
||||
limb z11[10];
|
||||
limb z2_5_0[10];
|
||||
limb z2_10_0[10];
|
||||
limb z2_20_0[10];
|
||||
limb z2_50_0[10];
|
||||
limb z2_100_0[10];
|
||||
limb t0[10];
|
||||
limb t1[10];
|
||||
int i;
|
||||
|
||||
/* 2 */ fsquare(z2,z);
|
||||
/* 4 */ fsquare(t1,z2);
|
||||
/* 8 */ fsquare(t0,t1);
|
||||
/* 9 */ fmul(z9,t0,z);
|
||||
/* 11 */ fmul(z11,z9,z2);
|
||||
/* 22 */ fsquare(t0,z11);
|
||||
/* 2^5 - 2^0 = 31 */ fmul(z2_5_0,t0,z9);
|
||||
|
||||
/* 2^6 - 2^1 */ fsquare(t0,z2_5_0);
|
||||
/* 2^7 - 2^2 */ fsquare(t1,t0);
|
||||
/* 2^8 - 2^3 */ fsquare(t0,t1);
|
||||
/* 2^9 - 2^4 */ fsquare(t1,t0);
|
||||
/* 2^10 - 2^5 */ fsquare(t0,t1);
|
||||
/* 2^10 - 2^0 */ fmul(z2_10_0,t0,z2_5_0);
|
||||
|
||||
/* 2^11 - 2^1 */ fsquare(t0,z2_10_0);
|
||||
/* 2^12 - 2^2 */ fsquare(t1,t0);
|
||||
/* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
|
||||
/* 2^20 - 2^0 */ fmul(z2_20_0,t1,z2_10_0);
|
||||
|
||||
/* 2^21 - 2^1 */ fsquare(t0,z2_20_0);
|
||||
/* 2^22 - 2^2 */ fsquare(t1,t0);
|
||||
/* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
|
||||
/* 2^40 - 2^0 */ fmul(t0,t1,z2_20_0);
|
||||
|
||||
/* 2^41 - 2^1 */ fsquare(t1,t0);
|
||||
/* 2^42 - 2^2 */ fsquare(t0,t1);
|
||||
/* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
|
||||
/* 2^50 - 2^0 */ fmul(z2_50_0,t0,z2_10_0);
|
||||
|
||||
/* 2^51 - 2^1 */ fsquare(t0,z2_50_0);
|
||||
/* 2^52 - 2^2 */ fsquare(t1,t0);
|
||||
/* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
|
||||
/* 2^100 - 2^0 */ fmul(z2_100_0,t1,z2_50_0);
|
||||
|
||||
/* 2^101 - 2^1 */ fsquare(t1,z2_100_0);
|
||||
/* 2^102 - 2^2 */ fsquare(t0,t1);
|
||||
/* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
|
||||
/* 2^200 - 2^0 */ fmul(t1,t0,z2_100_0);
|
||||
|
||||
/* 2^201 - 2^1 */ fsquare(t0,t1);
|
||||
/* 2^202 - 2^2 */ fsquare(t1,t0);
|
||||
/* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
|
||||
/* 2^250 - 2^0 */ fmul(t0,t1,z2_50_0);
|
||||
|
||||
/* 2^251 - 2^1 */ fsquare(t1,t0);
|
||||
/* 2^252 - 2^2 */ fsquare(t0,t1);
|
||||
/* 2^253 - 2^3 */ fsquare(t1,t0);
|
||||
/* 2^254 - 2^4 */ fsquare(t0,t1);
|
||||
/* 2^255 - 2^5 */ fsquare(t1,t0);
|
||||
/* 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];
|
||||
uint8_t e[32];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 32; ++i) e[i] = secret[i];
|
||||
|
||||
fexpand(bp, basepoint);
|
||||
cmult(x, z, e, bp);
|
||||
crecip(zmone, z);
|
||||
fmul(z, x, zmone);
|
||||
freduce_coefficients(z);
|
||||
fcontract(mypublic, z);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef CURVE25519_DONNA_H
|
||||
#define CURVE25519_DONNA_H
|
||||
|
||||
extern int curve25519_donna(uint8_t *, const uint8_t *, const uint8_t *);
|
||||
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@@ -1,52 +0,0 @@
|
||||
package textsecure;
|
||||
|
||||
option java_package = "org.whispersystems.textsecure.push";
|
||||
option java_outer_classname = "PushMessageProtos";
|
||||
|
||||
message IncomingPushMessageSignal {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
CIPHERTEXT = 1;
|
||||
KEY_EXCHANGE = 2;
|
||||
PREKEY_BUNDLE = 3;
|
||||
PLAINTEXT = 4;
|
||||
}
|
||||
optional Type type = 1;
|
||||
optional string source = 2;
|
||||
optional uint32 sourceDevice = 7;
|
||||
optional string relay = 3;
|
||||
optional uint64 timestamp = 5;
|
||||
optional bytes message = 6; // Contains an encrypted PushMessageContent
|
||||
// repeated string destinations = 4; // No longer supported
|
||||
}
|
||||
|
||||
message PushMessageContent {
|
||||
message AttachmentPointer {
|
||||
optional fixed64 id = 1;
|
||||
optional string contentType = 2;
|
||||
optional bytes key = 3;
|
||||
}
|
||||
|
||||
message GroupContext {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
UPDATE = 1;
|
||||
DELIVER = 2;
|
||||
QUIT = 3;
|
||||
}
|
||||
optional bytes id = 1;
|
||||
optional Type type = 2;
|
||||
optional string name = 3;
|
||||
repeated string members = 4;
|
||||
optional AttachmentPointer avatar = 5;
|
||||
}
|
||||
|
||||
enum Flags {
|
||||
END_SESSION = 1;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
optional GroupContext group = 3;
|
||||
optional uint32 flags = 4;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package textsecure;
|
||||
|
||||
option java_package = "org.whispersystems.textsecure.storage";
|
||||
option java_outer_classname = "StorageProtos";
|
||||
|
||||
message SessionStructure {
|
||||
message Chain {
|
||||
optional bytes senderEphemeral = 1;
|
||||
optional bytes senderEphemeralPrivate = 2;
|
||||
|
||||
message ChainKey {
|
||||
optional uint32 index = 1;
|
||||
optional bytes key = 2;
|
||||
}
|
||||
|
||||
optional ChainKey chainKey = 3;
|
||||
|
||||
message MessageKey {
|
||||
optional uint32 index = 1;
|
||||
optional bytes cipherKey = 2;
|
||||
optional bytes macKey = 3;
|
||||
}
|
||||
|
||||
repeated MessageKey messageKeys = 4;
|
||||
}
|
||||
|
||||
message PendingKeyExchange {
|
||||
optional uint32 sequence = 1;
|
||||
optional bytes localBaseKey = 2;
|
||||
optional bytes localBaseKeyPrivate = 3;
|
||||
optional bytes localEphemeralKey = 4;
|
||||
optional bytes localEphemeralKeyPrivate = 5;
|
||||
optional bytes localIdentityKey = 7;
|
||||
optional bytes localIdentityKeyPrivate = 8;
|
||||
}
|
||||
|
||||
message PendingPreKey {
|
||||
optional uint32 preKeyId = 1;
|
||||
optional bytes baseKey = 2;
|
||||
}
|
||||
|
||||
optional uint32 sessionVersion = 1;
|
||||
optional bytes localIdentityPublic = 2;
|
||||
optional bytes remoteIdentityPublic = 3;
|
||||
|
||||
optional bytes rootKey = 4;
|
||||
optional uint32 previousCounter = 5;
|
||||
|
||||
optional Chain senderChain = 6;
|
||||
repeated Chain receiverChains = 7;
|
||||
|
||||
optional PendingKeyExchange pendingKeyExchange = 8;
|
||||
optional PendingPreKey pendingPreKey = 9;
|
||||
|
||||
optional uint32 remoteRegistrationId = 10;
|
||||
optional uint32 localRegistrationId = 11;
|
||||
|
||||
optional bool needsRefresh = 12;
|
||||
}
|
||||
|
||||
message RecordStructure {
|
||||
optional SessionStructure currentSession = 1;
|
||||
repeated SessionStructure previousSessions = 2;
|
||||
}
|
||||
|
||||
message PreKeyRecordStructure {
|
||||
optional uint32 id = 1;
|
||||
optional bytes publicKey = 2;
|
||||
optional bytes privateKey = 3;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
all:
|
||||
protoc --java_out=../src/ IncomingPushMessageSignal.proto WhisperTextProtocol.proto LocalStorageProtocol.proto
|
||||
@@ -1,26 +0,0 @@
|
||||
package textsecure;
|
||||
|
||||
option java_package = "org.whispersystems.textsecure.crypto.protocol";
|
||||
option java_outer_classname = "WhisperProtos";
|
||||
|
||||
message WhisperMessage {
|
||||
optional bytes ephemeralKey = 1;
|
||||
optional uint32 counter = 2;
|
||||
optional uint32 previousCounter = 3;
|
||||
optional bytes ciphertext = 4;
|
||||
}
|
||||
|
||||
message PreKeyWhisperMessage {
|
||||
optional uint32 registrationId = 5;
|
||||
optional uint32 preKeyId = 1;
|
||||
optional bytes baseKey = 2;
|
||||
optional bytes identityKey = 3;
|
||||
optional bytes message = 4; // WhisperMessage
|
||||
}
|
||||
|
||||
message KeyExchangeMessage {
|
||||
optional uint32 id = 1;
|
||||
optional bytes baseKey = 2;
|
||||
optional bytes ephemeralKey = 3;
|
||||
optional bytes identityKey = 4;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -1,159 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Encrypts push attachments.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class AttachmentCipher {
|
||||
|
||||
static final int CIPHER_KEY_SIZE = 32;
|
||||
static final int MAC_KEY_SIZE = 32;
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final Cipher cipher;
|
||||
private final Mac mac;
|
||||
|
||||
public AttachmentCipher() {
|
||||
this.cipherKey = initializeRandomCipherKey();
|
||||
this.macKey = initializeRandomMacKey();
|
||||
this.cipher = initializeCipher();
|
||||
this.mac = initializeMac();
|
||||
}
|
||||
|
||||
public AttachmentCipher(byte[] combinedKeyMaterial) {
|
||||
byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE);
|
||||
this.cipherKey = new SecretKeySpec(parts[0], "AES");
|
||||
this.macKey = new SecretKeySpec(parts[1], "HmacSHA256");
|
||||
this.cipher = initializeCipher();
|
||||
this.mac = initializeMac();
|
||||
}
|
||||
|
||||
public byte[] getCombinedKeyMaterial() {
|
||||
return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded());
|
||||
}
|
||||
|
||||
public byte[] encrypt(byte[] plaintext) {
|
||||
try {
|
||||
this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey);
|
||||
this.mac.init(this.macKey);
|
||||
|
||||
byte[] ciphertext = this.cipher.doFinal(plaintext);
|
||||
byte[] iv = this.cipher.getIV();
|
||||
byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext));
|
||||
|
||||
return Util.combine(iv, ciphertext, mac);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] ciphertext)
|
||||
throws InvalidMacException, InvalidMessageException
|
||||
{
|
||||
try {
|
||||
if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) {
|
||||
throw new InvalidMessageException("Message too short!");
|
||||
}
|
||||
|
||||
byte[][] ciphertextParts = Util.split(ciphertext,
|
||||
this.cipher.getBlockSize(),
|
||||
ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(),
|
||||
this.mac.getMacLength());
|
||||
|
||||
this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength());
|
||||
byte[] ourMac = this.mac.doFinal();
|
||||
|
||||
if (!Arrays.equals(ourMac, ciphertextParts[2])) {
|
||||
throw new InvalidMacException("Mac doesn't match!");
|
||||
}
|
||||
|
||||
this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey,
|
||||
new IvParameterSpec(ciphertextParts[0]));
|
||||
|
||||
return cipher.doFinal(ciphertextParts[1]);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Mac initializeMac() {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
return mac;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher initializeCipher() {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKeySpec initializeRandomCipherKey() {
|
||||
byte[] key = new byte[CIPHER_KEY_SIZE];
|
||||
Util.getSecureRandom().nextBytes(key);
|
||||
return new SecretKeySpec(key, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec initializeRandomMacKey() {
|
||||
byte[] key = new byte[MAC_KEY_SIZE];
|
||||
Util.getSecureRandom().nextBytes(key);
|
||||
return new SecretKeySpec(key, "HmacSHA256");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Class for streaming an encrypted push attachment off disk.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class AttachmentCipherInputStream extends FileInputStream {
|
||||
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private Cipher cipher;
|
||||
private boolean done;
|
||||
private long totalDataSize;
|
||||
private long totalRead;
|
||||
private byte[] overflowBuffer;
|
||||
|
||||
public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
super(file);
|
||||
|
||||
try {
|
||||
byte[][] parts = Util.split(combinedKeyMaterial,
|
||||
AttachmentCipher.CIPHER_KEY_SIZE,
|
||||
AttachmentCipher.MAC_KEY_SIZE);
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(parts[1], "HmacSHA256"));
|
||||
|
||||
if (file.length() <= BLOCK_SIZE + mac.getMacLength()) {
|
||||
throw new InvalidMessageException("Message shorter than crypto overhead!");
|
||||
}
|
||||
|
||||
verifyMac(file, mac);
|
||||
|
||||
byte[] iv = new byte[BLOCK_SIZE];
|
||||
readFully(iv);
|
||||
|
||||
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(parts[0], "AES"), new IvParameterSpec(iv));
|
||||
|
||||
this.done = false;
|
||||
this.totalRead = 0;
|
||||
this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidMacException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
if (totalRead != totalDataSize) return readIncremental(buffer, offset, length);
|
||||
else if (!done) return readFinal(buffer, offset, length);
|
||||
else return -1;
|
||||
}
|
||||
|
||||
private int readFinal(byte[] buffer, int offset, int length) throws IOException {
|
||||
try {
|
||||
int flourish = cipher.doFinal(buffer, offset);
|
||||
|
||||
done = true;
|
||||
return flourish;
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
Log.w("EncryptingPartInputStream", e);
|
||||
throw new IOException("Illegal block size exception!");
|
||||
} catch (ShortBufferException e) {
|
||||
Log.w("EncryptingPartInputStream", e);
|
||||
throw new IOException("Short buffer exception!");
|
||||
} catch (BadPaddingException e) {
|
||||
Log.w("EncryptingPartInputStream", e);
|
||||
throw new IOException("Bad padding exception!");
|
||||
}
|
||||
}
|
||||
|
||||
private int readIncremental(byte[] buffer, int offset, int length) throws IOException {
|
||||
int readLength = 0;
|
||||
if (null != overflowBuffer) {
|
||||
if (overflowBuffer.length > length) {
|
||||
System.arraycopy(overflowBuffer, 0, buffer, offset, length);
|
||||
overflowBuffer = Arrays.copyOfRange(overflowBuffer, length, overflowBuffer.length);
|
||||
return length;
|
||||
} else if (overflowBuffer.length == length) {
|
||||
System.arraycopy(overflowBuffer, 0, buffer, offset, length);
|
||||
overflowBuffer = null;
|
||||
return length;
|
||||
} else {
|
||||
System.arraycopy(overflowBuffer, 0, buffer, offset, overflowBuffer.length);
|
||||
readLength += overflowBuffer.length;
|
||||
offset += readLength;
|
||||
length -= readLength;
|
||||
overflowBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (length + totalRead > totalDataSize)
|
||||
length = (int)(totalDataSize - totalRead);
|
||||
|
||||
byte[] internalBuffer = new byte[length];
|
||||
int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize());
|
||||
totalRead += read;
|
||||
|
||||
try {
|
||||
int outputLen = cipher.getOutputSize(read);
|
||||
|
||||
if (outputLen <= length) {
|
||||
readLength += cipher.update(internalBuffer, 0, read, buffer, offset);
|
||||
return readLength;
|
||||
}
|
||||
|
||||
byte[] transientBuffer = new byte[outputLen];
|
||||
outputLen = cipher.update(internalBuffer, 0, read, transientBuffer, 0);
|
||||
if (outputLen <= length) {
|
||||
System.arraycopy(transientBuffer, 0, buffer, offset, outputLen);
|
||||
readLength += outputLen;
|
||||
} else {
|
||||
System.arraycopy(transientBuffer, 0, buffer, offset, length);
|
||||
overflowBuffer = Arrays.copyOfRange(transientBuffer, length, outputLen);
|
||||
readLength += length;
|
||||
}
|
||||
return readLength;
|
||||
} catch (ShortBufferException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMac(File file, Mac mac) throws FileNotFoundException, InvalidMacException {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
int remainingData = (int) file.length() - mac.getMacLength();
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
while (remainingData > 0) {
|
||||
int read = fin.read(buffer, 0, Math.min(buffer.length, remainingData));
|
||||
mac.update(buffer, 0, read);
|
||||
remainingData -= read;
|
||||
}
|
||||
|
||||
byte[] ourMac = mac.doFinal();
|
||||
byte[] theirMac = new byte[mac.getMacLength()];
|
||||
Util.readFully(fin, theirMac);
|
||||
|
||||
if (!Arrays.equals(ourMac, theirMac)) {
|
||||
throw new InvalidMacException("MAC doesn't match!");
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
throw new InvalidMacException(e1);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFully(byte[] buffer) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
for (;;) {
|
||||
int read = super.read(buffer, offset, buffer.length - offset);
|
||||
|
||||
if (read + offset < buffer.length) offset += read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class DuplicateMessageException extends Exception {
|
||||
public DuplicateMessageException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
/**
|
||||
* A class for representing an identity key.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class IdentityKey implements Parcelable, SerializableKey {
|
||||
|
||||
public static final Parcelable.Creator<IdentityKey> CREATOR = new Parcelable.Creator<IdentityKey>() {
|
||||
public IdentityKey createFromParcel(Parcel in) {
|
||||
try {
|
||||
return new IdentityKey(in);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityKey[] newArray(int size) {
|
||||
return new IdentityKey[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static final int NIST_SIZE = 1 + ECPublicKey.KEY_SIZE;
|
||||
|
||||
private ECPublicKey publicKey;
|
||||
|
||||
public IdentityKey(ECPublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public IdentityKey(Parcel in) throws InvalidKeyException {
|
||||
int length = in.readInt();
|
||||
byte[] serialized = new byte[length];
|
||||
|
||||
in.readByteArray(serialized);
|
||||
initializeFromSerialized(serialized, 0);
|
||||
}
|
||||
|
||||
public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
initializeFromSerialized(bytes, offset);
|
||||
}
|
||||
|
||||
public ECPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException {
|
||||
if ((bytes[offset] & 0xff) == 1) {
|
||||
this.publicKey = Curve.decodePoint(bytes, offset +1);
|
||||
} else {
|
||||
this.publicKey = Curve.decodePoint(bytes, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return publicKey.serialize();
|
||||
}
|
||||
|
||||
public String getFingerprint() {
|
||||
return Hex.toString(publicKey.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof IdentityKey)) return false;
|
||||
|
||||
return publicKey.equals(((IdentityKey) other).getPublicKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return publicKey.hashCode();
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
byte[] serialized = this.serialize();
|
||||
dest.writeInt(serialized.length);
|
||||
dest.writeByteArray(serialized);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
|
||||
|
||||
/**
|
||||
* Holder for public and private identity key pair.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class IdentityKeyPair {
|
||||
|
||||
private final IdentityKey publicKey;
|
||||
private final ECPrivateKey privateKey;
|
||||
|
||||
public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) {
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public IdentityKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public ECPrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class InvalidKeyException extends Exception {
|
||||
|
||||
public InvalidKeyException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidKeyException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidKeyException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidKeyException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class InvalidMacException extends Exception {
|
||||
|
||||
public InvalidMacException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidMacException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidMacException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidMacException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class InvalidMessageException extends Exception {
|
||||
|
||||
public InvalidMessageException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidMessageException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidMessageException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidMessageException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class InvalidVersionException extends Exception {
|
||||
|
||||
public InvalidVersionException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public class LegacyMessageException extends Exception {
|
||||
public LegacyMessageException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve25519;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
||||
import org.whispersystems.textsecure.util.Medium;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PreKeyUtil {
|
||||
|
||||
public static final int BATCH_SIZE = 100;
|
||||
|
||||
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
|
||||
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
|
||||
int preKeyIdOffset = getNextPreKeyId(context);
|
||||
|
||||
for (int i=0;i<BATCH_SIZE;i++) {
|
||||
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
|
||||
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
|
||||
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
|
||||
|
||||
record.save();
|
||||
records.add(record);
|
||||
}
|
||||
|
||||
setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
|
||||
return records;
|
||||
}
|
||||
|
||||
public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) {
|
||||
if (PreKeyRecord.hasRecord(context, Medium.MAX_VALUE)) {
|
||||
try {
|
||||
return new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
Log.w("PreKeyUtil", e);
|
||||
PreKeyRecord.delete(context, Medium.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
|
||||
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
|
||||
|
||||
record.save();
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
// public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
|
||||
// List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
|
||||
// File directory = getPreKeysDirectory(context);
|
||||
// String[] keyRecordIds = directory.list();
|
||||
//
|
||||
// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
|
||||
//
|
||||
// for (String keyRecordId : keyRecordIds) {
|
||||
// try {
|
||||
// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
|
||||
// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
|
||||
// }
|
||||
// } catch (InvalidKeyIdException e) {
|
||||
// Log.w("PreKeyUtil", e);
|
||||
// new File(getPreKeysDirectory(context), keyRecordId).delete();
|
||||
// } catch (NumberFormatException nfe) {
|
||||
// Log.w("PreKeyUtil", nfe);
|
||||
// new File(getPreKeysDirectory(context), keyRecordId).delete();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return records;
|
||||
// }
|
||||
//
|
||||
// public static void clearPreKeys(Context context) {
|
||||
// File directory = getPreKeysDirectory(context);
|
||||
// String[] keyRecords = directory.list();
|
||||
//
|
||||
// for (String keyRecord : keyRecords) {
|
||||
// new File(directory, keyRecord).delete();
|
||||
// }
|
||||
// }
|
||||
|
||||
private static void setNextPreKeyId(Context context, int id) {
|
||||
try {
|
||||
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
||||
FileOutputStream fout = new FileOutputStream(nextFile);
|
||||
fout.write(new Gson().toJson(new PreKeyIndex(id)).getBytes());
|
||||
fout.close();
|
||||
} catch (IOException e) {
|
||||
Log.w("PreKeyUtil", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getNextPreKeyId(Context context) {
|
||||
try {
|
||||
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
|
||||
|
||||
if (!nextFile.exists()) {
|
||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
||||
} else {
|
||||
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
|
||||
PreKeyIndex index = new Gson().fromJson(reader, PreKeyIndex.class);
|
||||
reader.close();
|
||||
return index.nextPreKeyId;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w("PreKeyUtil", e);
|
||||
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
private static File getPreKeysDirectory(Context context) {
|
||||
File directory = new File(context.getFilesDir(), PreKeyRecord.PREKEY_DIRECTORY);
|
||||
|
||||
if (!directory.exists())
|
||||
directory.mkdirs();
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private static class PreKeyRecordIdComparator implements Comparator<String> {
|
||||
@Override
|
||||
public int compare(String lhs, String rhs) {
|
||||
if (lhs.equals(PreKeyIndex.FILE_NAME)) return -1;
|
||||
else if (rhs.equals(PreKeyIndex.FILE_NAME)) return 1;
|
||||
|
||||
try {
|
||||
long lhsLong = Long.parseLong(lhs);
|
||||
long rhsLong = Long.parseLong(rhs);
|
||||
|
||||
if (lhsLong < rhsLong) return -1;
|
||||
else if (lhsLong > rhsLong) return 1;
|
||||
else return 0;
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PreKeyIndex {
|
||||
public static final String FILE_NAME = "index.dat";
|
||||
|
||||
private int nextPreKeyId;
|
||||
|
||||
public PreKeyIndex() {}
|
||||
|
||||
public PreKeyIndex(int nextPreKeyId) {
|
||||
this.nextPreKeyId = nextPreKeyId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
public interface SerializableKey {
|
||||
public byte[] serialize();
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessage;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
|
||||
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.storage.SessionState;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class SessionCipher {
|
||||
|
||||
private static final Object SESSION_LOCK = new Object();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final RecipientDevice recipient;
|
||||
|
||||
public static SessionCipher createFor(Context context,
|
||||
MasterSecret masterSecret,
|
||||
RecipientDevice recipient)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
return new SessionCipher(context, masterSecret, recipient);
|
||||
} else {
|
||||
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SessionCipher(Context context, MasterSecret masterSecret, RecipientDevice recipient) {
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public CiphertextMessage encrypt(byte[] paddedMessage) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
ChainKey chainKey = sessionState.getSenderChainKey();
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
|
||||
int previousCounter = sessionState.getPreviousCounter();
|
||||
|
||||
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
|
||||
CiphertextMessage ciphertextMessage = new WhisperMessage(messageKeys.getMacKey(),
|
||||
senderEphemeral, chainKey.getIndex(),
|
||||
previousCounter, ciphertextBody);
|
||||
|
||||
if (sessionState.hasPendingPreKey()) {
|
||||
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
|
||||
int localRegistrationId = sessionState.getLocalRegistrationId();
|
||||
|
||||
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
|
||||
pendingPreKey.second,
|
||||
sessionState.getLocalIdentityKey(),
|
||||
(WhisperMessage) ciphertextMessage);
|
||||
}
|
||||
|
||||
sessionState.setSenderChainKey(chainKey.getNextChainKey());
|
||||
sessionRecord.save();
|
||||
|
||||
return ciphertextMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] decodedMessage)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||
SessionState sessionState = sessionRecord.getSessionState();
|
||||
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
|
||||
|
||||
try {
|
||||
byte[] plaintext = decrypt(sessionState, decodedMessage);
|
||||
sessionRecord.save();
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SessionCipherV2", e);
|
||||
}
|
||||
|
||||
for (SessionState previousState : previousStates) {
|
||||
try {
|
||||
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
|
||||
byte[] plaintext = decrypt(previousState, decodedMessage);
|
||||
sessionRecord.save();
|
||||
|
||||
return plaintext;
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("SessionCipherV2", e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidMessageException("No valid sessions.");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
|
||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||
{
|
||||
if (!sessionState.hasSenderChain()) {
|
||||
throw new InvalidMessageException("Uninitialized session!");
|
||||
}
|
||||
|
||||
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
|
||||
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
|
||||
int counter = ciphertextMessage.getCounter();
|
||||
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
|
||||
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
|
||||
chainKey, counter);
|
||||
|
||||
ciphertextMessage.verifyMac(messageKeys.getMacKey());
|
||||
|
||||
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
|
||||
|
||||
sessionState.clearPendingPreKey();
|
||||
|
||||
return plaintext;
|
||||
|
||||
}
|
||||
|
||||
public int getRemoteRegistrationId() {
|
||||
synchronized (SESSION_LOCK) {
|
||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||
return sessionRecord.getSessionState().getRemoteRegistrationId();
|
||||
}
|
||||
}
|
||||
|
||||
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
if (sessionState.hasReceiverChain(theirEphemeral)) {
|
||||
return sessionState.getReceiverChainKey(theirEphemeral);
|
||||
} else {
|
||||
RootKey rootKey = sessionState.getRootKey();
|
||||
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
|
||||
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
|
||||
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
|
||||
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
|
||||
|
||||
sessionState.setRootKey(senderChain.first);
|
||||
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
|
||||
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
|
||||
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
|
||||
|
||||
return receiverChain.second;
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
|
||||
ECPublicKey theirEphemeral,
|
||||
ChainKey chainKey, int counter)
|
||||
throws InvalidMessageException, DuplicateMessageException
|
||||
{
|
||||
if (chainKey.getIndex() > counter) {
|
||||
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
|
||||
return sessionState.removeMessageKeys(theirEphemeral, counter);
|
||||
} else {
|
||||
throw new DuplicateMessageException("Received message with old counter: " +
|
||||
chainKey.getIndex() + " , " + counter);
|
||||
}
|
||||
}
|
||||
|
||||
if (chainKey.getIndex() - counter > 2000) {
|
||||
throw new InvalidMessageException("Over 2000 messages into the future!");
|
||||
}
|
||||
|
||||
while (chainKey.getIndex() < counter) {
|
||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||
sessionState.setMessageKeys(theirEphemeral, messageKeys);
|
||||
chainKey = chainKey.getNextChainKey();
|
||||
}
|
||||
|
||||
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
|
||||
return chainKey.getMessageKeys();
|
||||
}
|
||||
|
||||
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
|
||||
messageKeys.getCipherKey(),
|
||||
messageKeys.getCounter());
|
||||
|
||||
return cipher.doFinal(plaintext);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
|
||||
try {
|
||||
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
|
||||
messageKeys.getCipherKey(),
|
||||
messageKeys.getCounter());
|
||||
return cipher.doFinal(cipherText);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
|
||||
byte[] ivBytes = new byte[16];
|
||||
Conversions.intToByteArray(ivBytes, 0, counter);
|
||||
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
cipher.init(mode, key, iv);
|
||||
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SessionRecordV2 getSessionRecord() {
|
||||
return new SessionRecordV2(context, masterSecret, recipient);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public interface TransportDetails {
|
||||
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding);
|
||||
public byte[] getPaddedMessageBody(byte[] messageBody);
|
||||
|
||||
public byte[] getEncodedMessage(byte[] messageWithMac);
|
||||
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto.ecc;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
|
||||
public class Curve {
|
||||
|
||||
public static final int DJB_TYPE = 0x05;
|
||||
|
||||
public static ECKeyPair generateKeyPair(boolean ephemeral) {
|
||||
return Curve25519.generateKeyPair(ephemeral);
|
||||
}
|
||||
|
||||
public static ECPublicKey decodePoint(byte[] bytes, int offset)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
int type = bytes[offset];
|
||||
|
||||
if (type == DJB_TYPE) {
|
||||
return Curve25519.decodePoint(bytes, offset);
|
||||
} else {
|
||||
throw new InvalidKeyException("Unknown key type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public static ECPrivateKey decodePrivatePoint(byte[] bytes) {
|
||||
return new DjbECPrivateKey(bytes);
|
||||
}
|
||||
|
||||
public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
if (publicKey.getType() != privateKey.getType()) {
|
||||
throw new InvalidKeyException("Public and private keys must be of the same type!");
|
||||
}
|
||||
|
||||
if (publicKey.getType() == DJB_TYPE) {
|
||||
return Curve25519.calculateAgreement(publicKey, privateKey);
|
||||
} else {
|
||||
throw new InvalidKeyException("Unknown type: " + publicKey.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto.ecc;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class Curve25519 {
|
||||
|
||||
static {
|
||||
System.loadLibrary("curve25519");
|
||||
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final SecureRandom random;
|
||||
|
||||
private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic);
|
||||
private static native byte[] generatePublicKey(byte[] privateKey);
|
||||
private static native byte[] generatePrivateKey(byte[] random, boolean ephemeral);
|
||||
|
||||
public static ECKeyPair generateKeyPair(boolean ephemeral) {
|
||||
byte[] privateKey = generatePrivateKey(ephemeral);
|
||||
byte[] publicKey = generatePublicKey(privateKey);
|
||||
|
||||
return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey));
|
||||
}
|
||||
|
||||
static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) {
|
||||
return calculateAgreement(((DjbECPrivateKey)privateKey).getPrivateKey(),
|
||||
((DjbECPublicKey)publicKey).getPublicKey());
|
||||
}
|
||||
|
||||
static ECPublicKey decodePoint(byte[] encoded, int offset)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
int type = encoded[offset] & 0xFF;
|
||||
byte[] keyBytes = new byte[32];
|
||||
System.arraycopy(encoded, offset+1, keyBytes, 0, keyBytes.length);
|
||||
|
||||
if (type != Curve.DJB_TYPE) {
|
||||
throw new InvalidKeyException("Bad key type: " + type);
|
||||
}
|
||||
|
||||
return new DjbECPublicKey(keyBytes);
|
||||
}
|
||||
|
||||
private static byte[] generatePrivateKey(boolean ephemeral) {
|
||||
byte[] privateKey = new byte[32];
|
||||
random.nextBytes(privateKey);
|
||||
|
||||
return generatePrivateKey(privateKey, ephemeral);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto.ecc;
|
||||
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DjbECPublicKey implements ECPublicKey {
|
||||
|
||||
private final byte[] publicKey;
|
||||
|
||||
DjbECPublicKey(byte[] publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
byte[] type = {Curve.DJB_TYPE};
|
||||
return Util.combine(type, publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return Curve.DJB_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof DjbECPublicKey)) return false;
|
||||
|
||||
DjbECPublicKey that = (DjbECPublicKey)other;
|
||||
return Arrays.equals(this.publicKey, that.publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ECPublicKey another) {
|
||||
return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey));
|
||||
}
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto.ecc;
|
||||
|
||||
public class ECKeyPair {
|
||||
|
||||
private final ECPublicKey publicKey;
|
||||
private final ECPrivateKey privateKey;
|
||||
|
||||
public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) {
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public ECPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public ECPrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class DerivedSecrets {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
|
||||
public DerivedSecrets(SecretKeySpec cipherKey, SecretKeySpec macKey) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class HKDF {
|
||||
|
||||
private static final int HASH_OUTPUT_SIZE = 32;
|
||||
private static final int KEY_MATERIAL_SIZE = 64;
|
||||
|
||||
private static final int CIPHER_KEYS_OFFSET = 0;
|
||||
private static final int MAC_KEYS_OFFSET = 32;
|
||||
|
||||
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] info) {
|
||||
byte[] salt = new byte[HASH_OUTPUT_SIZE];
|
||||
return deriveSecrets(inputKeyMaterial, salt, info);
|
||||
}
|
||||
|
||||
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info) {
|
||||
byte[] prk = extract(salt, inputKeyMaterial);
|
||||
byte[] okm = expand(prk, info, KEY_MATERIAL_SIZE);
|
||||
|
||||
SecretKeySpec cipherKey = deriveCipherKey(okm);
|
||||
SecretKeySpec macKey = deriveMacKey(okm);
|
||||
|
||||
return new DerivedSecrets(cipherKey, macKey);
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveCipherKey(byte[] okm) {
|
||||
byte[] cipherKey = new byte[32];
|
||||
System.arraycopy(okm, CIPHER_KEYS_OFFSET, cipherKey, 0, cipherKey.length);
|
||||
return new SecretKeySpec(cipherKey, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveMacKey(byte[] okm) {
|
||||
byte[] macKey = new byte[32];
|
||||
System.arraycopy(okm, MAC_KEYS_OFFSET, macKey, 0, macKey.length);
|
||||
return new SecretKeySpec(macKey, "HmacSHA256");
|
||||
}
|
||||
|
||||
private byte[] extract(byte[] salt, byte[] inputKeyMaterial) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(salt, "HmacSHA256"));
|
||||
return mac.doFinal(inputKeyMaterial);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] expand(byte[] prk, byte[] info, int outputSize) {
|
||||
try {
|
||||
int iterations = (int)Math.ceil((double)outputSize/(double)HASH_OUTPUT_SIZE);
|
||||
byte[] mixin = new byte[0];
|
||||
ByteArrayOutputStream results = new ByteArrayOutputStream();
|
||||
|
||||
for (int i=0;i<iterations;i++) {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(prk, "HmacSHA256"));
|
||||
|
||||
mac.update(mixin);
|
||||
if (info != null) {
|
||||
mac.update(info);
|
||||
}
|
||||
mac.update((byte)i);
|
||||
|
||||
byte[] stepResult = mac.doFinal();
|
||||
results.write(stepResult, 0, stepResult.length);
|
||||
|
||||
mixin = stepResult;
|
||||
}
|
||||
|
||||
return results.toByteArray();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.crypto.kdf;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class NKDF {
|
||||
|
||||
public static final int LEGACY_CIPHER_KEY_LENGTH = 16;
|
||||
public static final int LEGACY_MAC_KEY_LENGTH = 20;
|
||||
|
||||
public DerivedSecrets deriveSecrets(byte[] sharedSecret, boolean isLowEnd)
|
||||
{
|
||||
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
|
||||
SecretKeySpec macKey = deriveMacSecret(cipherKey);
|
||||
|
||||
return new DerivedSecrets(cipherKey, macKey);
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, byte[] sharedSecret) {
|
||||
byte[] derivedBytes = deriveBytes(sharedSecret, LEGACY_CIPHER_KEY_LENGTH * 2);
|
||||
byte[] cipherSecret = new byte[LEGACY_CIPHER_KEY_LENGTH];
|
||||
|
||||
if (isLowEnd) {
|
||||
System.arraycopy(derivedBytes, LEGACY_CIPHER_KEY_LENGTH, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH);
|
||||
} else {
|
||||
System.arraycopy(derivedBytes, 0, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH);
|
||||
}
|
||||
|
||||
return new SecretKeySpec(cipherSecret, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] secret = md.digest(key.getEncoded());
|
||||
|
||||
return new SecretKeySpec(secret, "HmacSHA1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("SHA-1 Not Supported!",e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] deriveBytes(byte[] seed, int bytesNeeded) {
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w("NKDF", e);
|
||||
throw new IllegalArgumentException("SHA-256 Not Supported!");
|
||||
}
|
||||
|
||||
int rounds = bytesNeeded / md.getDigestLength();
|
||||
|
||||
for (int i=1;i<=rounds;i++) {
|
||||
byte[] roundBytes = Conversions.intToByteArray(i);
|
||||
md.update(roundBytes);
|
||||
md.update(seed);
|
||||
}
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.protocol;
|
||||
|
||||
public interface CiphertextMessage {
|
||||
|
||||
public static final int UNSUPPORTED_VERSION = 1;
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
public static final int WHISPER_TYPE = 2;
|
||||
public static final int PREKEY_TYPE = 3;
|
||||
|
||||
// This should be the worst case (worse than V2). So not always accurate, but good enough for padding.
|
||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
|
||||
|
||||
public byte[] serialize();
|
||||
public int getType();
|
||||
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.LegacyMessageException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
public class PreKeyWhisperMessage implements CiphertextMessage {
|
||||
|
||||
private final int version;
|
||||
private final int registrationId;
|
||||
private final int preKeyId;
|
||||
private final ECPublicKey baseKey;
|
||||
private final IdentityKey identityKey;
|
||||
private final WhisperMessage message;
|
||||
private final byte[] serialized;
|
||||
|
||||
public PreKeyWhisperMessage(byte[] serialized)
|
||||
throws InvalidMessageException, InvalidVersionException
|
||||
{
|
||||
try {
|
||||
this.version = Conversions.highBitsToInt(serialized[0]);
|
||||
|
||||
if (this.version > CiphertextMessage.CURRENT_VERSION) {
|
||||
throw new InvalidVersionException("Unknown version: " + this.version);
|
||||
}
|
||||
|
||||
WhisperProtos.PreKeyWhisperMessage preKeyWhisperMessage
|
||||
= WhisperProtos.PreKeyWhisperMessage.parseFrom(ByteString.copyFrom(serialized, 1,
|
||||
serialized.length-1));
|
||||
|
||||
if (!preKeyWhisperMessage.hasPreKeyId() ||
|
||||
!preKeyWhisperMessage.hasBaseKey() ||
|
||||
!preKeyWhisperMessage.hasIdentityKey() ||
|
||||
!preKeyWhisperMessage.hasMessage())
|
||||
{
|
||||
throw new InvalidMessageException("Incomplete message.");
|
||||
}
|
||||
|
||||
this.serialized = serialized;
|
||||
this.registrationId = preKeyWhisperMessage.getRegistrationId();
|
||||
this.preKeyId = preKeyWhisperMessage.getPreKeyId();
|
||||
this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0);
|
||||
this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0));
|
||||
this.message = new WhisperMessage(preKeyWhisperMessage.getMessage().toByteArray());
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (LegacyMessageException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreKeyWhisperMessage(int registrationId, int preKeyId, ECPublicKey baseKey,
|
||||
IdentityKey identityKey, WhisperMessage message)
|
||||
{
|
||||
this.version = CiphertextMessage.CURRENT_VERSION;
|
||||
this.registrationId = registrationId;
|
||||
this.preKeyId = preKeyId;
|
||||
this.baseKey = baseKey;
|
||||
this.identityKey = identityKey;
|
||||
this.message = message;
|
||||
|
||||
byte[] versionBytes = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, this.version)};
|
||||
byte[] messageBytes = WhisperProtos.PreKeyWhisperMessage.newBuilder()
|
||||
.setPreKeyId(preKeyId)
|
||||
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
|
||||
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
|
||||
.setMessage(ByteString.copyFrom(message.serialize()))
|
||||
.setRegistrationId(registrationId)
|
||||
.build().toByteArray();
|
||||
|
||||
this.serialized = Util.combine(versionBytes, messageBytes);
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
public int getPreKeyId() {
|
||||
return preKeyId;
|
||||
}
|
||||
|
||||
public ECPublicKey getBaseKey() {
|
||||
return baseKey;
|
||||
}
|
||||
|
||||
public WhisperMessage getWhisperMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return CiphertextMessage.PREKEY_TYPE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.protocol;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.LegacyMessageException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.ParseException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class WhisperMessage implements CiphertextMessage {
|
||||
|
||||
private static final int MAC_LENGTH = 8;
|
||||
|
||||
private final ECPublicKey senderEphemeral;
|
||||
private final int counter;
|
||||
private final int previousCounter;
|
||||
private final byte[] ciphertext;
|
||||
private final byte[] serialized;
|
||||
|
||||
public WhisperMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
|
||||
try {
|
||||
byte[][] messageParts = Util.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
|
||||
byte version = messageParts[0][0];
|
||||
byte[] message = messageParts[1];
|
||||
byte[] mac = messageParts[2];
|
||||
|
||||
if (Conversions.highBitsToInt(version) <= CiphertextMessage.UNSUPPORTED_VERSION) {
|
||||
throw new LegacyMessageException("Legacy message: " + Conversions.highBitsToInt(version));
|
||||
}
|
||||
|
||||
if (Conversions.highBitsToInt(version) != CURRENT_VERSION) {
|
||||
throw new InvalidMessageException("Unknown version: " + Conversions.highBitsToInt(version));
|
||||
}
|
||||
|
||||
WhisperProtos.WhisperMessage whisperMessage = WhisperProtos.WhisperMessage.parseFrom(message);
|
||||
|
||||
if (!whisperMessage.hasCiphertext() ||
|
||||
!whisperMessage.hasCounter() ||
|
||||
!whisperMessage.hasEphemeralKey())
|
||||
{
|
||||
throw new InvalidMessageException("Incomplete message.");
|
||||
}
|
||||
|
||||
this.serialized = serialized;
|
||||
this.senderEphemeral = Curve.decodePoint(whisperMessage.getEphemeralKey().toByteArray(), 0);
|
||||
this.counter = whisperMessage.getCounter();
|
||||
this.previousCounter = whisperMessage.getPreviousCounter();
|
||||
this.ciphertext = whisperMessage.getCiphertext().toByteArray();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WhisperMessage(SecretKeySpec macKey, ECPublicKey senderEphemeral,
|
||||
int counter, int previousCounter, byte[] ciphertext)
|
||||
{
|
||||
byte[] version = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
|
||||
byte[] message = WhisperProtos.WhisperMessage.newBuilder()
|
||||
.setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize()))
|
||||
.setCounter(counter)
|
||||
.setPreviousCounter(previousCounter)
|
||||
.setCiphertext(ByteString.copyFrom(ciphertext))
|
||||
.build().toByteArray();
|
||||
byte[] mac = getMac(macKey, Util.combine(version, message));
|
||||
|
||||
this.serialized = Util.combine(version, message, mac);
|
||||
this.senderEphemeral = senderEphemeral;
|
||||
this.counter = counter;
|
||||
this.previousCounter = previousCounter;
|
||||
this.ciphertext = ciphertext;
|
||||
}
|
||||
|
||||
public ECPublicKey getSenderEphemeral() {
|
||||
return senderEphemeral;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public void verifyMac(SecretKeySpec macKey)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
byte[][] parts = Util.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH);
|
||||
byte[] ourMac = getMac(macKey, parts[0]);
|
||||
byte[] theirMac = parts[1];
|
||||
|
||||
if (!MessageDigest.isEqual(ourMac, theirMac)) {
|
||||
throw new InvalidMessageException("Bad Mac!");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getMac(SecretKeySpec macKey, byte[] serialized) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(macKey);
|
||||
|
||||
byte[] fullMac = mac.doFinal(serialized);
|
||||
return Util.trim(fullMac, MAC_LENGTH);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (java.security.InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return CiphertextMessage.WHISPER_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isLegacy(byte[] message) {
|
||||
return message != null && message.length >= 1 &&
|
||||
Conversions.highBitsToInt(message[0]) <= CiphertextMessage.UNSUPPORTED_VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.ratchet;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||
import org.whispersystems.textsecure.crypto.kdf.HKDF;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ChainKey {
|
||||
|
||||
private static final byte[] MESSAGE_KEY_SEED = {0x01};
|
||||
private static final byte[] CHAIN_KEY_SEED = {0x02};
|
||||
|
||||
private final byte[] key;
|
||||
private final int index;
|
||||
|
||||
public ChainKey(byte[] key, int index) {
|
||||
this.key = key;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public byte[] getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public ChainKey getNextChainKey() {
|
||||
byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED);
|
||||
return new ChainKey(nextKey, index + 1);
|
||||
}
|
||||
|
||||
public MessageKeys getMessageKeys() {
|
||||
HKDF kdf = new HKDF();
|
||||
byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED);
|
||||
DerivedSecrets keyMaterial = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes());
|
||||
|
||||
return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index);
|
||||
}
|
||||
|
||||
private byte[] getBaseMaterial(byte[] seed) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
|
||||
return mac.doFinal(seed);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.ratchet;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class MessageKeys {
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final int counter;
|
||||
|
||||
public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) {
|
||||
this.cipherKey = cipherKey;
|
||||
this.macKey = macKey;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public SecretKeySpec getCipherKey() {
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
public SecretKeySpec getMacKey() {
|
||||
return macKey;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.ratchet;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||
import org.whispersystems.textsecure.crypto.kdf.HKDF;
|
||||
import org.whispersystems.textsecure.storage.SessionState;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RatchetingSession {
|
||||
|
||||
public static void initializeSession(SessionState sessionState,
|
||||
ECKeyPair ourBaseKey,
|
||||
ECPublicKey theirBaseKey,
|
||||
ECKeyPair ourEphemeralKey,
|
||||
ECPublicKey theirEphemeralKey,
|
||||
IdentityKeyPair ourIdentityKey,
|
||||
IdentityKey theirIdentityKey)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
|
||||
initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey,
|
||||
ourIdentityKey, theirIdentityKey);
|
||||
} else {
|
||||
initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey,
|
||||
ourEphemeralKey, ourIdentityKey, theirIdentityKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeSessionAsAlice(SessionState sessionState,
|
||||
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
|
||||
ECPublicKey theirEphemeralKey,
|
||||
IdentityKeyPair ourIdentityKey,
|
||||
IdentityKey theirIdentityKey)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
sessionState.setRemoteIdentityKey(theirIdentityKey);
|
||||
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
|
||||
|
||||
ECKeyPair sendingKey = Curve.generateKeyPair(true);
|
||||
Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
|
||||
Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey);
|
||||
|
||||
sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second);
|
||||
sessionState.setSenderChain(sendingKey, sendingChain.second);
|
||||
sessionState.setRootKey(sendingChain.first);
|
||||
}
|
||||
|
||||
private static void initializeSessionAsBob(SessionState sessionState,
|
||||
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
|
||||
ECKeyPair ourEphemeralKey,
|
||||
IdentityKeyPair ourIdentityKey,
|
||||
IdentityKey theirIdentityKey)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
sessionState.setRemoteIdentityKey(theirIdentityKey);
|
||||
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
|
||||
|
||||
Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey,
|
||||
ourIdentityKey, theirIdentityKey);
|
||||
|
||||
sessionState.setSenderChain(ourEphemeralKey, sendingChain.second);
|
||||
sessionState.setRootKey(sendingChain.first);
|
||||
}
|
||||
|
||||
private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice,
|
||||
ECKeyPair ourEphemeral, ECPublicKey theirEphemeral,
|
||||
IdentityKeyPair ourIdentity, IdentityKey theirIdentity)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
try {
|
||||
ByteArrayOutputStream secrets = new ByteArrayOutputStream();
|
||||
|
||||
if (isAlice) {
|
||||
secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey()));
|
||||
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey()));
|
||||
} else {
|
||||
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey()));
|
||||
secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey()));
|
||||
}
|
||||
|
||||
secrets.write(Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey()));
|
||||
|
||||
DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(secrets.toByteArray(),
|
||||
"WhisperText".getBytes());
|
||||
|
||||
return new Pair<RootKey, ChainKey>(new RootKey(derivedSecrets.getCipherKey().getEncoded()),
|
||||
new ChainKey(derivedSecrets.getMacKey().getEncoded(), 0));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAlice(ECPublicKey ourBaseKey, ECPublicKey theirBaseKey,
|
||||
ECPublicKey ourEphemeralKey, ECPublicKey theirEphemeralKey)
|
||||
{
|
||||
if (ourEphemeralKey.equals(ourBaseKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (theirEphemeralKey.equals(theirBaseKey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isLowEnd(ourBaseKey, theirBaseKey);
|
||||
}
|
||||
|
||||
private static boolean isLowEnd(ECPublicKey ourKey, ECPublicKey theirKey) {
|
||||
return ourKey.compareTo(theirKey) < 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.whispersystems.textsecure.crypto.ratchet;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||
import org.whispersystems.textsecure.crypto.kdf.HKDF;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RootKey {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
public RootKey(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Pair<RootKey, ChainKey> createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral)
|
||||
throws InvalidKeyException
|
||||
{
|
||||
HKDF kdf = new HKDF();
|
||||
byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey());
|
||||
DerivedSecrets keys = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes());
|
||||
RootKey newRootKey = new RootKey(keys.getCipherKey().getEncoded());
|
||||
ChainKey newChainKey = new ChainKey(keys.getMacKey().getEncoded(), 0);
|
||||
|
||||
return new Pair<RootKey, ChainKey>(newRootKey, newChainKey);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.directory;
|
||||
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* A simple bloom filter implementation that backs the RedPhone directory.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class BloomFilter {
|
||||
|
||||
private final MappedByteBuffer buffer;
|
||||
private final long length;
|
||||
private final int hashCount;
|
||||
|
||||
public BloomFilter(File bloomFilter, int hashCount)
|
||||
throws IOException
|
||||
{
|
||||
this.length = bloomFilter.length();
|
||||
this.buffer = new FileInputStream(bloomFilter).getChannel()
|
||||
.map(FileChannel.MapMode.READ_ONLY, 0, length);
|
||||
this.hashCount = hashCount;
|
||||
}
|
||||
|
||||
public int getHashCount() {
|
||||
return hashCount;
|
||||
}
|
||||
|
||||
private boolean isBitSet(long bitIndex) {
|
||||
int byteInQuestion = this.buffer.get((int)(bitIndex / 8));
|
||||
int bitOffset = (0x01 << (bitIndex % 8));
|
||||
|
||||
return (byteInQuestion & bitOffset) > 0;
|
||||
}
|
||||
|
||||
public boolean contains(String entity) {
|
||||
try {
|
||||
for (int i=0;i<this.hashCount;i++) {
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
mac.init(new SecretKeySpec((i+"").getBytes(), "HmacSHA1"));
|
||||
|
||||
byte[] hashValue = mac.doFinal(entity.getBytes());
|
||||
long bitIndex = Math.abs(Conversions.byteArrayToLong(hashValue, 0)) % (this.length * 8);
|
||||
|
||||
if (!isBitSet(bitIndex))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.whispersystems.textsecure.directory;
|
||||
|
||||
public class DirectoryDescriptor {
|
||||
private String version;
|
||||
private long capacity;
|
||||
private int hashCount;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public int getHashCount() {
|
||||
return hashCount;
|
||||
}
|
||||
|
||||
public long getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
private String url;
|
||||
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecure.directory;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
import com.google.thoughtcrimegson.JsonParseException;
|
||||
import com.google.thoughtcrimegson.annotations.SerializedName;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* Handles providing lookups, serializing, and deserializing the RedPhone directory.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
|
||||
public class NumberFilter {
|
||||
|
||||
private static NumberFilter instance;
|
||||
|
||||
public synchronized static NumberFilter getInstance(Context context) {
|
||||
if (instance == null)
|
||||
instance = NumberFilter.deserializeFromFile(context);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static final String DIRECTORY_META_FILE = "directory.stat";
|
||||
|
||||
private File bloomFilter;
|
||||
private String version;
|
||||
private long capacity;
|
||||
private int hashCount;
|
||||
private Context context;
|
||||
|
||||
private NumberFilter(Context context, File bloomFilter, long capacity,
|
||||
int hashCount, String version)
|
||||
{
|
||||
this.context = context.getApplicationContext();
|
||||
this.bloomFilter = bloomFilter;
|
||||
this.capacity = capacity;
|
||||
this.hashCount = hashCount;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public synchronized boolean containsNumber(String number) {
|
||||
try {
|
||||
if (bloomFilter == null) return false;
|
||||
else if (number == null || number.length() == 0) return false;
|
||||
|
||||
return new BloomFilter(bloomFilter, hashCount).contains(number);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("NumberFilter", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean containsNumbers(List<String> numbers) {
|
||||
try {
|
||||
if (bloomFilter == null) return false;
|
||||
if (numbers == null || numbers.size() == 0) return false;
|
||||
|
||||
BloomFilter filter = new BloomFilter(bloomFilter, hashCount);
|
||||
|
||||
for (String number : numbers) {
|
||||
if (!filter.contains(number)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
Log.w("NumberFilter", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void update(DirectoryDescriptor descriptor, File compressedData) {
|
||||
try {
|
||||
File uncompressed = File.createTempFile("directory", ".dat", context.getFilesDir());
|
||||
FileInputStream fin = new FileInputStream (compressedData);
|
||||
GZIPInputStream gin = new GZIPInputStream(fin);
|
||||
FileOutputStream out = new FileOutputStream(uncompressed);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
while ((read = gin.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
out.close();
|
||||
compressedData.delete();
|
||||
|
||||
update(uncompressed, descriptor.getCapacity(), descriptor.getHashCount(), descriptor.getVersion());
|
||||
} catch (IOException ioe) {
|
||||
Log.w("NumberFilter", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void update(File bloomFilter, long capacity, int hashCount, String version)
|
||||
{
|
||||
if (this.bloomFilter != null)
|
||||
this.bloomFilter.delete();
|
||||
|
||||
this.bloomFilter = bloomFilter;
|
||||
this.capacity = capacity;
|
||||
this.hashCount = hashCount;
|
||||
this.version = version;
|
||||
|
||||
serializeToFile(context);
|
||||
}
|
||||
|
||||
private void serializeToFile(Context context) {
|
||||
if (this.bloomFilter == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
FileOutputStream fout = context.openFileOutput(DIRECTORY_META_FILE, 0);
|
||||
NumberFilterStorage storage = new NumberFilterStorage(bloomFilter.getAbsolutePath(),
|
||||
capacity, hashCount, version);
|
||||
|
||||
storage.serializeToStream(fout);
|
||||
fout.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.w("NumberFilter", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private static NumberFilter deserializeFromFile(Context context) {
|
||||
try {
|
||||
FileInputStream fis = context.openFileInput(DIRECTORY_META_FILE);
|
||||
NumberFilterStorage storage = NumberFilterStorage.fromStream(fis);
|
||||
|
||||
if (storage == null) return new NumberFilter(context, null, 0, 0, "0");
|
||||
else return new NumberFilter(context,
|
||||
new File(storage.getDataPath()),
|
||||
storage.getCapacity(),
|
||||
storage.getHashCount(),
|
||||
storage.getVersion());
|
||||
} catch (IOException ioe) {
|
||||
Log.w("NumberFilter", ioe);
|
||||
return new NumberFilter(context, null, 0, 0, "0");
|
||||
}
|
||||
}
|
||||
|
||||
private static class NumberFilterStorage {
|
||||
@SerializedName("data_path")
|
||||
private String dataPath;
|
||||
|
||||
@SerializedName("capacity")
|
||||
private long capacity;
|
||||
|
||||
@SerializedName("hash_count")
|
||||
private int hashCount;
|
||||
|
||||
@SerializedName("version")
|
||||
private String version;
|
||||
|
||||
public NumberFilterStorage(String dataPath, long capacity, int hashCount, String version) {
|
||||
this.dataPath = dataPath;
|
||||
this.capacity = capacity;
|
||||
this.hashCount = hashCount;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getDataPath() {
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
public long getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int getHashCount() {
|
||||
return hashCount;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void serializeToStream(OutputStream out) throws IOException {
|
||||
out.write(new Gson().toJson(this).getBytes());
|
||||
}
|
||||
|
||||
public static NumberFilterStorage fromStream(InputStream in) throws IOException {
|
||||
try {
|
||||
return new Gson().fromJson(new BufferedReader(new InputStreamReader(in)),
|
||||
NumberFilterStorage.class);
|
||||
} catch (JsonParseException jpe) {
|
||||
Log.w("NumberFilter", jpe);
|
||||
throw new IOException("JSON Parse Exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
public class AccountAttributes {
|
||||
|
||||
private String signalingKey;
|
||||
private boolean supportsSms;
|
||||
private int registrationId;
|
||||
|
||||
public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) {
|
||||
this.signalingKey = signalingKey;
|
||||
this.supportsSms = supportsSms;
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
|
||||
public AccountAttributes() {}
|
||||
|
||||
public String getSignalingKey() {
|
||||
return signalingKey;
|
||||
}
|
||||
|
||||
public boolean isSupportsSms() {
|
||||
return supportsSms;
|
||||
}
|
||||
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthorizationFailedException extends IOException {
|
||||
public AuthorizationFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
|
||||
public class ContactTokenDetails {
|
||||
|
||||
private String token;
|
||||
private String relay;
|
||||
private String number;
|
||||
private boolean supportsSms;
|
||||
|
||||
public ContactTokenDetails() {}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
|
||||
public boolean isSupportsSms() {
|
||||
return supportsSms;
|
||||
}
|
||||
|
||||
public void setNumber(String number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ContactTokenDetailsList {
|
||||
|
||||
private List<ContactTokenDetails> contacts;
|
||||
|
||||
public ContactTokenDetailsList() {}
|
||||
|
||||
public List<ContactTokenDetails> getContacts() {
|
||||
return contacts;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ContactTokenList {
|
||||
|
||||
private List<String> contacts;
|
||||
|
||||
public ContactTokenList(List<String> contacts) {
|
||||
this.contacts = contacts;
|
||||
}
|
||||
|
||||
public ContactTokenList() {}
|
||||
|
||||
public List<String> getContacts() {
|
||||
return contacts;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ExpectationFailedException extends IOException {
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class IncomingEncryptedPushMessage {
|
||||
|
||||
private static final int SUPPORTED_VERSION = 1;
|
||||
private static final int CIPHER_KEY_SIZE = 32;
|
||||
private static final int MAC_KEY_SIZE = 20;
|
||||
private static final int MAC_SIZE = 10;
|
||||
|
||||
private static final int VERSION_OFFSET = 0;
|
||||
private static final int VERSION_LENGTH = 1;
|
||||
private static final int IV_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
|
||||
private static final int IV_LENGTH = 16;
|
||||
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
|
||||
|
||||
private final IncomingPushMessage incomingPushMessage;
|
||||
|
||||
public IncomingEncryptedPushMessage(String message, String signalingKey)
|
||||
throws IOException, InvalidVersionException
|
||||
{
|
||||
byte[] ciphertext = Base64.decode(message);
|
||||
|
||||
if (ciphertext.length < VERSION_LENGTH || ciphertext[VERSION_OFFSET] != SUPPORTED_VERSION)
|
||||
throw new InvalidVersionException("Unsupported version!");
|
||||
|
||||
SecretKeySpec cipherKey = getCipherKey(signalingKey);
|
||||
SecretKeySpec macKey = getMacKey(signalingKey);
|
||||
|
||||
verifyMac(ciphertext, macKey);
|
||||
|
||||
byte[] plaintext = getPlaintext(ciphertext, cipherKey);
|
||||
IncomingPushMessageSignal signal = IncomingPushMessageSignal.parseFrom(plaintext);
|
||||
|
||||
this.incomingPushMessage = new IncomingPushMessage(signal);
|
||||
}
|
||||
|
||||
public IncomingPushMessage getIncomingPushMessage() {
|
||||
return incomingPushMessage;
|
||||
}
|
||||
|
||||
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
|
||||
try {
|
||||
byte[] ivBytes = new byte[IV_LENGTH];
|
||||
System.arraycopy(ciphertext, IV_OFFSET, ivBytes, 0, ivBytes.length);
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv);
|
||||
|
||||
return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET,
|
||||
ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
Log.w("IncomingEncryptedPushMessage", e);
|
||||
throw new IOException("Bad padding?");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMac(byte[] ciphertext, SecretKeySpec macKey) throws IOException {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(macKey);
|
||||
|
||||
if (ciphertext.length < MAC_SIZE + 1)
|
||||
throw new IOException("Invalid MAC!");
|
||||
|
||||
mac.update(ciphertext, 0, ciphertext.length - MAC_SIZE);
|
||||
|
||||
byte[] ourMacFull = mac.doFinal();
|
||||
byte[] ourMacBytes = new byte[MAC_SIZE];
|
||||
System.arraycopy(ourMacFull, 0, ourMacBytes, 0, ourMacBytes.length);
|
||||
|
||||
byte[] theirMacBytes = new byte[MAC_SIZE];
|
||||
System.arraycopy(ciphertext, ciphertext.length-MAC_SIZE, theirMacBytes, 0, theirMacBytes.length);
|
||||
|
||||
Log.w("IncomingEncryptedPushMessage", "Our MAC: " + Hex.toString(ourMacBytes));
|
||||
Log.w("IncomingEncryptedPushMessage", "Thr MAC: " + Hex.toString(theirMacBytes));
|
||||
|
||||
if (!Arrays.equals(ourMacBytes, theirMacBytes)) {
|
||||
throw new IOException("Invalid MAC compare!");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SecretKeySpec getCipherKey(String signalingKey) throws IOException {
|
||||
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
||||
byte[] cipherKey = new byte[CIPHER_KEY_SIZE];
|
||||
System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length);
|
||||
|
||||
return new SecretKeySpec(cipherKey, "AES");
|
||||
}
|
||||
|
||||
|
||||
private SecretKeySpec getMacKey(String signalingKey) throws IOException {
|
||||
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
||||
byte[] macKey = new byte[MAC_KEY_SIZE];
|
||||
System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length);
|
||||
|
||||
return new SecretKeySpec(macKey, "HmacSHA256");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
||||
|
||||
public class IncomingPushMessage implements Parcelable {
|
||||
|
||||
public static final Parcelable.Creator<IncomingPushMessage> CREATOR = new Parcelable.Creator<IncomingPushMessage>() {
|
||||
@Override
|
||||
public IncomingPushMessage createFromParcel(Parcel in) {
|
||||
return new IncomingPushMessage(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IncomingPushMessage[] newArray(int size) {
|
||||
return new IncomingPushMessage[size];
|
||||
}
|
||||
};
|
||||
|
||||
private int type;
|
||||
private String source;
|
||||
private int sourceDevice;
|
||||
private byte[] message;
|
||||
private long timestamp;
|
||||
private String relay;
|
||||
|
||||
private IncomingPushMessage(IncomingPushMessage message, byte[] body) {
|
||||
this.type = message.type;
|
||||
this.source = message.source;
|
||||
this.sourceDevice = message.sourceDevice;
|
||||
this.timestamp = message.timestamp;
|
||||
this.relay = message.relay;
|
||||
this.message = body;
|
||||
}
|
||||
|
||||
public IncomingPushMessage(IncomingPushMessageSignal signal) {
|
||||
this.type = signal.getType().getNumber();
|
||||
this.source = signal.getSource();
|
||||
this.sourceDevice = signal.getSourceDevice();
|
||||
this.message = signal.getMessage().toByteArray();
|
||||
this.timestamp = signal.getTimestamp();
|
||||
this.relay = signal.getRelay();
|
||||
}
|
||||
|
||||
public IncomingPushMessage(Parcel in) {
|
||||
this.type = in.readInt();
|
||||
this.source = in.readString();
|
||||
this.sourceDevice = in.readInt();
|
||||
|
||||
if (in.readInt() == 1) {
|
||||
this.relay = in.readString();
|
||||
}
|
||||
|
||||
this.message = new byte[in.readInt()];
|
||||
in.readByteArray(this.message);
|
||||
this.timestamp = in.readLong();
|
||||
}
|
||||
|
||||
public IncomingPushMessage(int type, String source, int sourceDevice,
|
||||
byte[] body, long timestamp)
|
||||
{
|
||||
this.type = type;
|
||||
this.source = source;
|
||||
this.sourceDevice = sourceDevice;
|
||||
this.message = body;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
|
||||
public long getTimestampMillis() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getSourceDevice() {
|
||||
return sourceDevice;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(type);
|
||||
dest.writeString(source);
|
||||
dest.writeInt(sourceDevice);
|
||||
dest.writeInt(relay == null ? 0 : 1);
|
||||
if (relay != null) {
|
||||
dest.writeString(relay);
|
||||
}
|
||||
dest.writeInt(message.length);
|
||||
dest.writeByteArray(message);
|
||||
dest.writeLong(timestamp);
|
||||
}
|
||||
|
||||
public IncomingPushMessage withBody(byte[] body) {
|
||||
return new IncomingPushMessage(this, body);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isSecureMessage() {
|
||||
return getType() == IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE;
|
||||
}
|
||||
|
||||
public boolean isPreKeyBundle() {
|
||||
return getType() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MismatchedDevices {
|
||||
private List<Integer> missingDevices;
|
||||
|
||||
private List<Integer> extraDevices;
|
||||
|
||||
public List<Integer> getMissingDevices() {
|
||||
return missingDevices;
|
||||
}
|
||||
|
||||
public List<Integer> getExtraDevices() {
|
||||
return extraDevices;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MismatchedDevicesException extends IOException {
|
||||
|
||||
private final MismatchedDevices mismatchedDevices;
|
||||
|
||||
public MismatchedDevicesException(MismatchedDevices mismatchedDevices) {
|
||||
this.mismatchedDevices = mismatchedDevices;
|
||||
}
|
||||
|
||||
public MismatchedDevices getMismatchedDevices() {
|
||||
return mismatchedDevices;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NotFoundException extends IOException {
|
||||
public NotFoundException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
public class OutgoingPushMessage {
|
||||
|
||||
private int type;
|
||||
private int destinationDeviceId;
|
||||
private int destinationRegistrationId;
|
||||
private String body;
|
||||
|
||||
public OutgoingPushMessage(PushAddress address, PushBody body) {
|
||||
this.type = body.getType();
|
||||
this.destinationDeviceId = address.getDeviceId();
|
||||
this.destinationRegistrationId = body.getRemoteRegistrationId();
|
||||
this.body = Base64.encodeBytes(body.getBody());
|
||||
}
|
||||
|
||||
public int getDestinationDeviceId() {
|
||||
return destinationDeviceId;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getDestinationRegistrationId() {
|
||||
return destinationRegistrationId;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class OutgoingPushMessageList {
|
||||
|
||||
private String destination;
|
||||
|
||||
private String relay;
|
||||
|
||||
private List<OutgoingPushMessage> messages;
|
||||
|
||||
public OutgoingPushMessageList(String destination, String relay, List<OutgoingPushMessage> messages) {
|
||||
this.destination = destination;
|
||||
this.relay = relay;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public List<OutgoingPushMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import com.google.thoughtcrimegson.GsonBuilder;
|
||||
import com.google.thoughtcrimegson.JsonDeserializationContext;
|
||||
import com.google.thoughtcrimegson.JsonDeserializer;
|
||||
import com.google.thoughtcrimegson.JsonElement;
|
||||
import com.google.thoughtcrimegson.JsonParseException;
|
||||
import com.google.thoughtcrimegson.JsonPrimitive;
|
||||
import com.google.thoughtcrimegson.JsonSerializationContext;
|
||||
import com.google.thoughtcrimegson.JsonSerializer;
|
||||
import com.google.thoughtcrimegson.annotations.Expose;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class PreKeyEntity {
|
||||
|
||||
@Expose(serialize = false)
|
||||
private int deviceId;
|
||||
|
||||
private int keyId;
|
||||
private ECPublicKey publicKey;
|
||||
private IdentityKey identityKey;
|
||||
private int registrationId;
|
||||
|
||||
public PreKeyEntity(int keyId, ECPublicKey publicKey, IdentityKey identityKey) {
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.identityKey = identityKey;
|
||||
this.registrationId = registrationId;
|
||||
}
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public int getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public ECPublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
public static String toJson(PreKeyEntity entity) {
|
||||
return getBuilder().create().toJson(entity);
|
||||
}
|
||||
|
||||
public static PreKeyEntity fromJson(String encoded) {
|
||||
return getBuilder().create().fromJson(encoded, PreKeyEntity.class);
|
||||
}
|
||||
|
||||
public static GsonBuilder getBuilder() {
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(ECPublicKey.class, new ECPublicKeyJsonAdapter());
|
||||
builder.registerTypeAdapter(IdentityKey.class, new IdentityKeyJsonAdapter());
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
private static class ECPublicKeyJsonAdapter
|
||||
implements JsonSerializer<ECPublicKey>, JsonDeserializer<ECPublicKey>
|
||||
{
|
||||
@Override
|
||||
public JsonElement serialize(ECPublicKey preKeyPublic, Type type,
|
||||
JsonSerializationContext jsonSerializationContext)
|
||||
{
|
||||
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(preKeyPublic.serialize()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECPublicKey deserialize(JsonElement jsonElement, Type type,
|
||||
JsonDeserializationContext jsonDeserializationContext)
|
||||
throws JsonParseException
|
||||
{
|
||||
try {
|
||||
return Curve.decodePoint(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new JsonParseException(e);
|
||||
} catch (IOException e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class IdentityKeyJsonAdapter
|
||||
implements JsonSerializer<IdentityKey>, JsonDeserializer<IdentityKey>
|
||||
{
|
||||
@Override
|
||||
public JsonElement serialize(IdentityKey identityKey, Type type,
|
||||
JsonSerializationContext jsonSerializationContext)
|
||||
{
|
||||
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(identityKey.serialize()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKey deserialize(JsonElement jsonElement, Type type,
|
||||
JsonDeserializationContext jsonDeserializationContext)
|
||||
throws JsonParseException
|
||||
{
|
||||
try {
|
||||
return new IdentityKey(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new JsonParseException(e);
|
||||
} catch (IOException e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PreKeyList {
|
||||
|
||||
private PreKeyEntity lastResortKey;
|
||||
private List<PreKeyEntity> keys;
|
||||
|
||||
public PreKeyList(PreKeyEntity lastResortKey, List<PreKeyEntity> keys) {
|
||||
this.keys = keys;
|
||||
this.lastResortKey = lastResortKey;
|
||||
}
|
||||
|
||||
public List<PreKeyEntity> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public static String toJson(PreKeyList entity) {
|
||||
return PreKeyEntity.getBuilder().create().toJson(entity);
|
||||
}
|
||||
|
||||
public static PreKeyList fromJson(String serialized) {
|
||||
return PreKeyEntity.getBuilder().create().fromJson(serialized, PreKeyList.class);
|
||||
}
|
||||
|
||||
public PreKeyEntity getLastResortKey() {
|
||||
return lastResortKey;
|
||||
}
|
||||
}
|
||||