Compare commits

...

269 Commits

Author SHA1 Message Date
Moxie Marlinspike
e53bbe8453 Bumping version to 2.1.2
// FREEBIE
2014-08-01 16:14:02 -07:00
Moxie Marlinspike
ecb67cd84f Updated language translations.
// FREEBIE
2014-08-01 14:41:00 -07:00
Moxie Marlinspike
861d27279d Whoops, add GcmRegistrationService to Manifest.
// FREEBIE
2014-08-01 14:33:17 -07:00
Veeti Paananen
bef5b8f3e9 Occupy all vertical space for emoji grid
Fixes the recent emoji list leaving an empty gap at the bottom of the
drawer depending on the number of items shown.
2014-08-01 13:40:19 -07:00
Veeti Paananen
9e74b5c892 Remove gray placeholder squares while loading emoji 2014-08-01 13:40:19 -07:00
Veeti Paananen
3597915d17 Add a backspace key to the emoji drawer 2014-08-01 13:40:19 -07:00
Veeti Paananen
40ce0cebe0 Fix emoji backwards compatibility recents crash
The old emoji drawer stored emoji with a .png suffix. Replace it during
list deserialization.
2014-07-31 02:49:36 +03:00
jhertel
26d58047b5 Update BUILDING.md
Correction of a typo and a few style incongruences.

// FREEBIE
Closes #1750
2014-07-28 09:36:03 -07:00
Jake McGinty
7d688846f9 Move default SMS and system import to "reminders"
// FREEBIE
Closes #1730
2014-07-27 01:09:39 -07:00
McLoo
acc7c4c1c6 Null check for cipher text to prevent NPE on decryption
Fixes #1703
Closes #1728
// FREEBIE
2014-07-26 23:02:11 -07:00
Jake McGinty
530ad7bc86 new emoji drawer
// FREEBIE
Closes #1746
2014-07-26 13:35:03 -07:00
Jake McGinty
bea3c33223 disable passphrase creation on registration
// FREEBIE
Closes #1726
2014-07-25 17:46:50 -07:00
Moxie Marlinspike
9ef14a0f64 Upgrade to new GCM API. 2014-07-23 15:40:45 -07:00
Moxie Marlinspike
c632b32ff8 Bumping version to 2.1.1 2014-07-19 11:13:18 -07:00
Moxie Marlinspike
40698212bb Create a Curve25519 asymmetric master secret for users without.
Fixes #1701
2014-07-18 22:16:12 -07:00
Veeti Paananen
19ae5043cc Add number of messages to the notification number attribute
Although not used by stock Android, many custom ROM's (and possibly OEM
versions?) have a setting to display the "number" count of a notification
overlayed on the status bar icon. Add support for this.

Closes #1637
2014-07-16 11:50:42 -07:00
rymdhund
d1dd50e31c Add date to saved media filenames
Fixes #1689
Closes #1693
2014-07-16 11:46:18 -07:00
Jake McGinty
23a1c1c8fa Upgrade to latest Android gradle plugin
// FREEBIE
Closes #1660
2014-07-16 11:12:51 -07:00
Moxie Marlinspike
3a62a8b428 Bumping version to 2.1.0 2014-07-15 10:05:15 -07:00
Moxie Marlinspike
cfac27645b Update language translations.
// FREEBIE
2014-07-14 18:44:34 -07:00
Jake McGinty
f6e04d0f89 use latest android number as recipient number
Fixes #791
// FREEBIE
2014-07-14 16:22:15 -07:00
Jake McGinty
61d18f49ad Merge pull request #1678 from veeti/check-icon
Add xxhdpi check drawable for notifications
2014-07-07 16:09:44 -05:00
Veeti Paananen
f26f89d63d Add xxhdpi check drawable for notifications
Fixes blurry mark as read icon on Android Wear. FREEBIE.
2014-07-05 02:40:00 +03:00
Moxie Marlinspike
66aad852f8 Merge pull request #1642 from mcginty/canonical-bunk-addresses
don't try to load recipients for each filter text
2014-06-25 08:06:00 -07:00
Jake McGinty
da0eb5a779 no longer load a recipient for each filter text
// FREEBIE
2014-06-24 20:33:04 -07:00
Jake McGinty
a82d2dfc5c Revert "change out key cached icon to be more unique"
This reverts commit d6d76fa953.
2014-06-24 19:20:16 -07:00
McLoo
d429f9113b Replace XML serializer in plaintext export
Fixes #342

- using regex pattern/matcher to escape chars below 0x0020 and
  above 0xd7ff
- using String.Replace to escape XML entities
- changed XmlPullParser from Xml.newPullParser() to
  XmlPullParserFactory parser to fix import on GB
2014-06-24 13:02:36 -07:00
Moxie Marlinspike
8f85eb1822 Remove unused files.
Fixes #1522

// FREEBIE
2014-06-24 08:32:59 -07:00
Moxie Marlinspike
358c923891 Merge pull request #1630 from mcginty/remove-keys-list
remove ReviewIdentitiesActivity
2014-06-23 11:17:17 -07:00
Lukas Barth
2d9cd8eb52 Fixing race condition and other mistakes. Fixes #1603.
// FREEBIE
2014-06-23 11:16:37 -07:00
Moxie Marlinspike
db1d846833 Merge pull request #1631 from mcginty/disable-encrypted-export
temporarily disable encrypted backup
2014-06-23 07:18:50 -07:00
Jake McGinty
5121ab0eed temporarily disable encrypted backup
// FREEBIE
2014-06-22 16:40:02 -07:00
Jake McGinty
f63f95404e remove ReviewIdentitiesActivity
// FREEBIE
2014-06-22 16:21:30 -07:00
Jake McGinty
622d8975fc add section about submitting useful bug reports 2014-06-20 12:24:36 -07:00
Moxie Marlinspike
81365eff36 Merge pull request #1614 from mcginty/contact-list-security
move FLAG_SECURE to PassphraseRequiredMixin
2014-06-17 21:47:00 -07:00
Florian Walch
453610c39f Add Travis CI config.
//FREEBIE
2014-06-17 14:13:42 -07:00
Jake McGinty
5ce6dc954a move FLAG_SECURE to PassphraseRequiredMixin
Fixes #1402
// FREEBIE
2014-06-16 20:41:13 -07:00
Özgür Emir
c85a8bbb38 Always show the time of the received message. 2014-06-16 15:57:39 -07:00
agrajaghh
0f9a6e6296 add custom phone number type 2014-06-16 15:27:33 -07:00
phenx-de
d8cb893681 Fixes "subtitle is not updated when select all is pressed"
// FREEBIE
2014-06-16 09:33:35 -07:00
Michael Kaiser
1ad54e7b88 Fix more leaked service connections
PassphraseRequiredMixin might check for a bound service at a time where
the bind has been requested but the service connection has not been
established yet, and therefore fail to call unbindService, leading to a
leaked service connection. This fixes #1518.
2014-06-15 19:28:09 -07:00
Moxie Marlinspike
0d35e2bfa9 Fix the "Tap for X fallback" labels. 2014-06-13 17:48:56 -07:00
Moxie Marlinspike
983bf672cf Fix UI side of broken MMS fallback.
1) Actually tell the SendReceiveService to send the MMS if it is
   one.

2) Display the correct string (SMS vs MMS) in the fallback dialog.
2014-06-13 17:39:29 -07:00
Moxie Marlinspike
1c2e1a07f5 Fixes for outgoing SMS/MMS direct and fallback behavior.
1) Correct MMS fallback settings.

2) Prevent SMS/MMS messages from leaking out under certain
   circumstances when they shouldn't.
2014-06-13 17:15:46 -07:00
Moxie Marlinspike
2d739a324e Validate MMS delivery destination.
We can't depend on validated Recipients anymore, so this adds
parity to the validation the SMS transport does now.

Fixes #1592
2014-06-13 16:15:33 -07:00
Moxie Marlinspike
ba1055df8e Correct contextual send language.
1) Use "secure" and "insecure" vs "encrypted" and "unencrypted.

2) Use MMS instead of SMS where appropriate.

Fixes #1602
2014-06-13 15:24:38 -07:00
phenx-de
a54d20f3ef Add "%s selected" subtitle to Conversation List batch mode. 2014-06-13 09:35:36 -07:00
phenx-de
ea0fa58265 Add preview of encryption channel in compose text hint. 2014-06-12 16:32:31 -07:00
Moxie Marlinspike
359fe280e8 Fix for broken build (*ahem* @phenx-de *ahem*) =)
// FREEBIE
2014-06-12 16:27:51 -07:00
Jake McGinty
34e147838a use apply for preferences instead of commit
// FREEBIE
2014-06-12 14:45:51 -07:00
phenx-de
d8e6a93584 Use contextual action bar menu for conversation items. 2014-06-12 14:27:34 -07:00
phenx-de
5ae8a7a8c4 Improved the warning icon: Higher resolution, better size. 2014-06-12 14:24:08 -07:00
Moxie Marlinspike
0e6773b4b7 Remove directory refresh preference.
This is present on the contact screen now, so there's no longer
any need for it here.
2014-06-12 11:58:24 -07:00
McLoo
fb13d33e2e Show drafts emojified when Android version is below KitKat 2014-06-12 11:30:50 -07:00
Pascal Hartig
92fd8ededd Sort contacts case-insentively
This fixes the case sensitive ordering of contacts in the view
for creating new conversations.

Fix #1502
2014-06-12 10:56:46 -07:00
agrajaghh
8713a85beb Add notification for key change event.
Fixes #1460
2014-06-12 10:40:46 -07:00
Marek Wehmer
9b82411c3d Better share intent handling.
1) Guess mime type from share intent EXTRA_STREAM uri.

2) Always include EXTRA_TEXT (if present)
2014-06-12 10:23:56 -07:00
Jake McGinty
16764f74fe reorganize readme
// FREEBIE
2014-06-12 10:02:18 -07:00
Veeti Paananen
bd889d8fa9 Reword the very confusing screen security setting 2014-06-12 09:22:39 -07:00
Moxie Marlinspike
d51adab76b Use "date sent" as timestamp for push, "date received" for SMS.
The "sent time" is not reliable on SMS messages.  This switches
to using "sent time" by default for push messages, but "received
time" for SMS messages.
2014-06-12 08:59:54 -07:00
Chris V
b990202468 Allow passphrase unlock from "unlock" keyboard action. 2014-06-11 21:45:03 -07:00
Pascal Hartig
7208018097 Clear search when opening the drawer
Before this change opening the drawer while a filter was active
would hide the search bar but keep the conversation list filtered,
so there was no indication of an active filter.
2014-06-11 18:12:07 -07:00
Moxie Marlinspike
c719a48a2c Move media attachment long-click event to context menu.
Long-click on a media attachment will now bring up the normal
context menu for a ConversationItem long-click, but with the
addition of a "save attachment" option.

This allows users to long-click on messages with media in them
and still see the other contextual menu options.

// FREEBIE
2014-06-11 18:04:14 -07:00
Moxie Marlinspike
68747142d6 Add correct contextual menu options on 'Send' button.
[Send TextSecure message | Send unencrypted SMS | Send encrypted SMS]

// FREEBIE
2014-06-11 15:34:01 -07:00
Lukas Barth
7c9282f306 Cache circle cropped photos on Recipient. 2014-06-11 12:33:57 -07:00
Lukas Barth
fa3cb871d0 Use ACTION_OPEN_DOCUMENT for Android >= KitKat.
Fixes #926.

We have to do this, since with the new Storage Access Framework,
otherwise we can open the Uri only *once*. This would work well
unless someone saves a draft and goes back to the conversation -
then the Uri is opened again without the required permissions.

See:

https://developer.android.com/guide/topics/providers/document-provider.html#client

...for details.
2014-06-11 11:58:55 -07:00
Moxie Marlinspike
a19899a11f Merge pull request #1589 from jlund/cyanogen-error-message
Fixing a typo in the WhisperPush error message
2014-06-11 09:20:50 -07:00
Joshua Lund
667da3b2cf Fixing a typo in the WhisperPush error message
// FREEBIE
2014-06-10 20:56:45 -06:00
Moxie Marlinspike
1a86483b7f Merge pull request #1581 from mcginty/android-studio-060
Upgrade android plugin to stay compatible with latest Android Studio
2014-06-10 09:07:59 -07:00
Jake McGinty
de90222c95 Upgrade android plugin to stay compatible with latest Android Studio
// FREEBIE
2014-06-09 23:31:52 -07:00
Moxie Marlinspike
7cf84e904a Merge pull request #1573 from Jabro/master
Added APN Settings for T-Mobile UK
2014-06-09 09:44:50 -07:00
Jabro
12e92b9cdf Added APN Settings for T-Mobile UK
Fixes #1558
2014-06-09 12:43:19 +02:00
Moxie Marlinspike
4153c8dae9 No need to verify a local module.
// FREEBIE
2014-06-06 09:14:43 -07:00
Moxie Marlinspike
ef72702f0d Include gradle-witness plugin for verifying dependencies. 2014-06-05 10:19:28 -07:00
Moxie Marlinspike
f5e2010455 Merge pull request #1554 from thoughtbox/patch-8
Capitalisation according to Android guidelines
2014-06-05 09:06:19 -07:00
thoughtbox
bc769debe2 Capitalisation according to Android guidelines
Not all updated capitalisations were correct.
2014-06-05 09:24:31 +02:00
Moxie Marlinspike
df1c96a662 Do country code detection if we can't get the full number off SIM. 2014-06-03 19:16:27 -07:00
Corbin Souffrant
db356a0ec9 Fixed capitalization inconsistensies with Android guidelines.
Fixes #673
2014-06-03 18:51:21 -07:00
Michael Bennett
468eb3382c Add sorting by default phone number
Currently the order of numbers is times contacted -> displayName ->
phone type (mobile vs. home, etc.). This adds whether the number has
been saved as the default number for a contact to sort numbers belonging
to the same contact.

Fixes #580
2014-06-03 18:18:41 -07:00
Moxie Marlinspike
12d217991c Use dynamic PBE iteration count.
Fixes #184
Fixes #247
2014-06-03 17:59:11 -07:00
Ruben Pollan
5785860631 Support for multiple APN settings on the same provider 2014-06-03 16:24:20 -07:00
Moxie Marlinspike
addea8d340 Validate recipients at send time rather than when constructed.
Fixes #665
2014-06-03 14:58:19 -07:00
Moxie Marlinspike
59899b1caf Merge pull request #1550 from mcginty/email-send-fix
send email addresses as mms
2014-06-03 12:46:08 -07:00
Jake McGinty
829097d891 send email addresses as mms
// FREEBIE
2014-06-03 12:35:56 -07:00
Moxie Marlinspike
d95bb21065 More ideology.
// FREEBIE
2014-06-01 17:05:21 -07:00
Moxie Marlinspike
0fbe765447 Expand ideology.
//FREEBIE
2014-06-01 10:22:31 -07:00
Moxie Marlinspike
f190321e40 Add some ideology to contributing.md
//FREEBIE
2014-05-31 15:05:59 -07:00
Michael Kaiser
1cb4d479f1 Finish activity before restarting it
The current activity needs to be finished before calling startActivity.
Otherwise, activities with launchMode singleTask (ConversationListActivity)
will receive a new Intent instead of getting restarted. And in response
to the new Intent, they will run onResume once again and trigger a second restart.

Fixes #1292
2014-05-20 10:16:48 -07:00
McLoo
023d776e96 Reactivate a group if a contact gets readded
Fixes #723 //FREEBIE

Removes the own number from group on leaving, to receive a proper
re-added message
2014-05-19 13:18:28 -07:00
Jake McGinty
ce7b8ab75a new passphrase prompt activity
// FREEBIE
2014-05-19 12:16:42 -07:00
agrajaghh
82bb0c07e8 Fix AlertDialog Background on Android 2.3 2014-05-16 17:18:57 -07:00
Manuel
d8d5848dae Change draw selector to background 2014-05-16 11:43:47 -07:00
Jake McGinty
542e1984c1 sanely handle duplicate contacts in db
// FREEBIE
2014-05-16 09:16:20 -07:00
Jake McGinty
dff6997a65 don't call replace() on null formattedNumber
Fixes #1397
// FREEBIE
2014-05-16 09:13:50 -07:00
Moxie Marlinspike
5bfe64752e Merge pull request #1495 from mcginty/icon-cached
change out key cached icon to be more unique
2014-05-16 08:30:31 -07:00
Jake McGinty
d6d76fa953 change out key cached icon to be more unique
Fixes #651
// FREEBIE
2014-05-14 17:52:49 -07:00
Sebastian
03ecd79fe0 fix receiving utf-8 characters in multimedia push messages
Throw AssertionError instead of logging and trying to recover
2014-05-01 15:06:44 -07:00
Moxie Marlinspike
7a3d509ef4 Merge pull request #1437 from mcginty/mms-npe
prevent NPE in MMS logic
2014-05-01 13:05:01 -07:00
Jake McGinty
7a54f33f68 Merge pull request #1354 from agrajaghh/fix_empty_contact_filter
Fix empty contact filter not working properly
2014-05-01 14:18:25 -05:00
Jake McGinty
d4b4667d5a prevent NPE in MMS logic
Fixes #1434
// FREEBIE
2014-04-28 14:05:05 -07:00
thoughtbox
08d899e2e1 Slightly more verbose "no legacy support" message. 2014-04-28 11:02:48 -07:00
Jake McGinty
716519f4b8 Merge pull request #1383 from jocelynthode/master
Add time to messages when they are within the week
2014-04-27 19:19:49 -07:00
Moxie Marlinspike
02d3760b31 Merge pull request #1368 from mcginty/contact-select-header-dark
fix contact selection header theming
2014-04-24 18:47:13 -07:00
Jocelyn Thode
521fbc77c6 Add time to messages when they are within the week
//FREEBIE
2014-04-17 16:10:27 +02:00
Moxie Marlinspike
0574ec170a Display legacy message error when V1 message is received. 2014-04-16 11:47:51 -07:00
Moxie Marlinspike
cebad39422 Collapse some v2 interfaces now that there's no v1. 2014-04-16 11:47:51 -07:00
Moxie Marlinspike
1d07ca3e6f Remove V1 code. 2014-04-16 11:47:51 -07:00
Moxie Marlinspike
ca8c950553 Bump version to 2.0.8
// FREEBIE
2014-04-15 15:59:47 -07:00
Jake McGinty
7349378d8d fix contact selection header theming
Fixes #1343
// FREEBIE
2014-04-14 17:43:13 -07:00
Moxie Marlinspike
dc9a9b14b2 Merge pull request #1367 from mcginty/routing-undo
revert RoutingActivity flags
2014-04-14 15:02:42 -07:00
Jake McGinty
df9afc4e7f revert RoutingActivity flags
// FREEBIE
2014-04-14 14:49:15 -07:00
agrajaghh
e9a50ce6c3 fix empty contact filter 2014-04-13 15:00:04 +02:00
Moxie Marlinspike
4d52d2ee36 Bumping version to 2.0.7 2014-04-11 10:58:39 -07:00
Moxie Marlinspike
c21e5b74f1 Updated language translations. 2014-04-11 10:13:23 -07:00
Moxie Marlinspike
c5b3b27c49 Merge pull request #1339 from mcginty/broken-build
fix old code breaking build
2014-04-11 10:13:05 -07:00
Jake McGinty
ccb0cc6baf fix old code breaking build
// FREEBIE
2014-04-11 10:00:30 -07:00
Moxie Marlinspike
ef77dc9d6d Merge pull request #1324 from mcginty/actionbar-cleanup
use normal means to set actionbar icon
2014-04-10 17:57:53 -07:00
Jake McGinty
454673cd0c Update ActionBar title on language change
Fixes #1329
// FREEBIE
2014-04-10 17:55:03 -07:00
Jake McGinty
0dc6ec294b use normal means to set actionbar icon
// FREEBIE
2014-04-10 17:52:58 -07:00
Santoso Wijaya
12dac6ccc3 ShareActivity, destruction of RecipientsPanel
// FREEBIE
2014-04-10 13:16:14 -07:00
Jake McGinty
e2f7c1529a group and contact list fixes
1) Updating a group without changing the avatar will keep that
   avatar

2) Prohibit adding non-push users to an existing push group

3) Add Android contacts to the same database. Takes a small amount
   more time and memory, but allows queries to not be a hack, and
   enables us to dedupe numbers in JB and higher devices.

// FREEBIE
2014-04-10 11:14:06 -07:00
Jake McGinty
b715debefc mark auto-fallback to sms as forcedSms
// FREEBIE
2014-04-10 10:50:35 -07:00
Moxie Marlinspike
4438b4ae69 Add a TrustManager that blacklists via serial numbers. 2014-04-09 20:50:32 -07:00
Jake McGinty
7b3f2c169a Fix issue with weird exiting animation from conversation
Fixes #1312
// FREEBIE
2014-04-08 10:57:17 -07:00
Matt Enright
7ceaf59bcc Display send date for incoming messages
Fixes #597.
2014-04-05 14:39:58 -07:00
Moxie Marlinspike
4caff2e511 Merge pull request #1297 from McLoo/master
Stop recycled bitmap crashes
2014-04-05 13:38:45 -07:00
McLoo
5e8e13ed5a Stop recycled bitmap crashes
Fixes #792
2014-04-04 23:49:12 +02:00
Moxie Marlinspike
4ea481f9dc Bumping version to 2.0.6
// FREEBIE
2014-04-04 09:27:05 -07:00
Moxie Marlinspike
e94fa0d752 Updated language translations.
// FREEBIE
2014-04-03 16:02:36 -07:00
Moxie Marlinspike
817070e76f Merge pull request #1288 from mcginty/send-icons-redux-redux
remove lock coloring in send icons
2014-04-03 15:11:33 -07:00
Jake McGinty
92b90cd798 remove lock coloring in send icons
// FREEBIE
2014-04-03 14:05:34 -07:00
Moxie Marlinspike
33ecc4d690 Add null check. // FREEBIE 2014-04-03 14:01:15 -07:00
Jake McGinty
7d5e66eb6e fix send icon merge botch, encryption icons logic
// FREEBIE
2014-04-03 12:12:59 -07:00
Moxie Marlinspike
d4ac0c077d Merge pull request #1248 from mcginty/wrong-group-secure
outgoing group MMS should not be encrypted and considered push
2014-04-03 11:22:00 -07:00
Jake McGinty
cbe87aa05c outgoing group MMS should never be encrypted
// FREEBIE
2014-04-03 11:21:13 -07:00
Moxie Marlinspike
9e2f82954f Merge pull request #1260 from mcginty/message-layout-lint
Conversation item tweaks
2014-04-03 10:07:23 -07:00
Jake McGinty
d4d684b670 simplified layouts 2014-04-03 09:59:32 -07:00
Moxie Marlinspike
c43ef8bce0 Merge pull request #1233 from mcginty/send-icons
new send icons, fix logic for disabling/enabling the button
2014-04-03 09:58:10 -07:00
Jake McGinty
54a882a11d new send icons, fix logic for disabling/enabling
// FREEBIE
2014-04-03 09:49:50 -07:00
Moxie Marlinspike
73e2f6ce59 Merge pull request #1272 from mcginty/flag-secure
hide screen security when unsupported
2014-04-02 22:14:08 -07:00
Moxie Marlinspike
54d2184c72 Merge pull request #1205 from mcginty/contact-select
refactor and improve contact selection
2014-04-02 22:11:44 -07:00
Jake McGinty
832763f695 UX for unencrypted fallback case 2014-04-02 22:10:51 -07:00
Moxie Marlinspike
40629a3bcf Fix decryption corner case when passphrase is disabled. 2014-04-02 22:10:51 -07:00
Moxie Marlinspike
5a3daf4846 Curve25519 keys to 1 mod 8 for ephemerals. 2014-04-02 22:10:51 -07:00
Moxie Marlinspike
fd1a18d2d0 Don't display duplicate push messages. 2014-04-02 22:10:50 -07:00
Moxie Marlinspike
ad5d6d5bb7 Add refresh path for PreKey queue. 2014-04-02 22:10:50 -07:00
Moxie Marlinspike
926d3c929f Handle simultaneous initiate protocol case.
1) Modify SessionRecord to store a list of "previous" sessions
   in addition to the current active session.  Previous sessions
   can be used for receiving messages, but not for sending
   messages.

2) When a possible "simultaneous initiate" is detected, push the
   current session onto the "previous session" stack instead of
   clearing it and starting over.

3) Additionally, mark the new session created on a received
   possible "simultaneous initiate" as stale for sending.  The
   next outgoing message would trigger a full prekey refresh.

4) Work to do: outgoing messages on the SMS transport should
   probably not use the existing session if it's marked stale
   for sending.  These messages need to fail and notify the user,
   similar to how we'll handle SMS fallback to push users before
   a prekey session is created.
2014-04-02 22:10:50 -07:00
Moxie Marlinspike
edc20883eb Merge pull request #1281 from thoughtbox/patch-6
Changed MMS Proxy setting for 24201 (Telenor)
2014-04-02 09:14:32 -07:00
thoughtbox
0d7363e36e Changed MMS Proxy setting for 24201 (Telenor)
Going forward, Telenor will be using FQDN.
2014-04-02 12:47:05 +02:00
Jake McGinty
ca6d8a8a0d refactor and improve contact selection
* unify single and multi contact selection activities
* follow android listview design recommendations more closely
* add contact photos to selection
* change indicator for push to be more obvious
* cache circle-cropped bitmaps
* dedupe numbers when contact has multiple of same phone number

// FREEBIE
2014-04-01 14:56:45 -07:00
Moxie Marlinspike
c414334059 Merge pull request #1228 from backspace/support-commas-in-contact-names
Remove commas in names in recipient list string
2014-03-31 08:39:15 -07:00
Moxie Marlinspike
24a38985cf Merge pull request #1249 from mcginty/convo-scroll
scroll to the new message in conversation
2014-03-31 08:14:26 -07:00
Brian Conway
60f2d4d6b6 Add APN for Aio wireless (USA).
Source: http://www.aiowireless.com/support/Apps-and-Services/Bring-Your-Own-Device-BYOD/customer/Bring-Your-Own-Android.html
2014-03-31 08:09:10 -07:00
Jake McGinty
06659fd98f hide screen security when unsupported
// FREEBIE
2014-03-30 02:28:38 -07:00
Jake McGinty
3c9c5213a7 Merge pull request #946 from DorianScholz/groupavatar
Use standard intent to select group avatar.
2014-03-28 12:07:30 -07:00
Dorian Scholz
a183f8d387 Fix #641 by using standard intent to select group avatar.
The ACTION_GET_CONTENT used with cropping is not supported on all devices.
To make this work more reliably I removed the cropping and MediaStore.EXTRA_OUTPUT.
The image is now read via getContentResolver().openInputStream() which should work on all device including KitKat/CM11.
2014-03-27 12:44:56 +01:00
Jake McGinty
216446c55b scroll to the new message in conversation
// FREEBIE
2014-03-26 15:12:01 -07:00
Buck Doyle
bc143059f6 Remove separators from names in recipient list
Fixes #1225 and #1174
2014-03-25 11:11:20 -07:00
Jake McGinty
33000582ed one more try at that one..
// FREEBIE
2014-03-25 03:31:44 -07:00
Jake McGinty
e651f352bb fix NPE in isPushDestination
// FREEBIE
2014-03-24 17:02:39 -07:00
Buck Doyle
cab4a06974 Remove commas in names in recipient list string
Fixes #1225
2014-03-23 15:02:21 -07:00
martinstingl
ccc1f5e9d6 Added the dependency "Android SDK Build-tools".
//FREEBIE
2014-03-20 18:06:58 -07:00
Moxie Marlinspike
b860aeff85 Minor ConversationList scrolling optimization. 2014-03-16 14:36:21 -07:00
Martin Ranta
34c885f08d Fixed a few localization names. 2014-03-16 10:06:26 -07:00
Moxie Marlinspike
71ab6f5c7d Merge pull request #1178 from backspace/extract-input-settings-string
Extract Input Settings preference header string.
2014-03-16 10:02:59 -07:00
Buck Doyle
61fbf382eb Extract Input Settings preference header string.
Fixes #1159

FREEBIE
2014-03-15 22:18:14 -04:00
Moxie Marlinspike
8b21f3f7d6 Bump version to 2.0.5 2014-03-14 10:18:13 -07:00
Moxie Marlinspike
941d0089f4 Add languages to selector 2014-03-14 09:40:58 -07:00
Moxie Marlinspike
8b8c6dd45f Updated translations
//FREEBIE
2014-03-14 09:40:43 -07:00
Jake McGinty
938545444e Merge pull request #984 from mcginty/sms-prefs
more precise sms controls
2014-03-13 21:03:02 -07:00
Jake McGinty
d827ab1b36 more precise sms controls
// FREEBIE
2014-03-13 20:59:20 -07:00
Jake McGinty
4701e59197 Merge pull request #1076 from phenx-de/fix-big-fontsize
Fix conversation list view for larger text sizes.
2014-03-13 13:05:56 -07:00
Moxie Marlinspike
2b2da84918 Merge pull request #1140 from psm14/bugs/group_mms_local_number
Also check cc for duplicates in group MMS
2014-03-12 18:58:08 -07:00
Pat McLaughlin
d229a4274c Also check cc for duplicates 2014-03-12 20:49:55 -04:00
3xo
64711771f0 Fix locale when using country codes. 2014-03-12 16:56:24 -07:00
Moxie Marlinspike
ad54d2a05f Modify string tag.
//FREEBIE
2014-03-12 09:57:32 -07:00
Wikinaut
068c40336c added Google Play Store text 2014-03-11 14:18:23 -07:00
Jake McGinty
11cfc4f1a1 upgrade gradle version
// FREEBIE
2014-03-11 01:05:24 -07:00
phenx-de
f51989b23e Fix conversation list view for larger text sizes. 2014-03-10 10:32:32 +01:00
Moxie Marlinspike
0b4fe84a41 Format-neutral compare numbers in MMS group with local device.
Closes #1018
2014-03-07 13:05:35 -08:00
Moxie Marlinspike
b855a1805f Mark message details on pending messages as such.
Closes #1034
2014-03-07 12:39:16 -08:00
3xo
339193af12 Give user the opportunity to chose no LED-notification. 2014-03-07 12:04:19 -08:00
Jake McGinty
574f33c92d Merge pull request #1069 from phenx-de/color-cleanup
Moved colors to the right place.
2014-03-07 12:00:49 -08:00
Manuel
b6a9eb5bf2 Closes #913: Message ID is -1 when called in aggregate 2014-03-07 11:59:53 -08:00
phenx-de
54b43b7536 Moved colors to the right place. 2014-03-07 11:03:22 +01:00
Moxie Marlinspike
37c9fb7cd1 Merge pull request #1053 from shenki/master
ConversationFragment: enable back button to dismiss message detail dialog
2014-03-06 20:46:43 -08:00
Joel Stanley
667d22bace ConversationFragment: enable back button to dismiss
The ConversationFragment has a AlertDialog for showing the message
details, which sets the cancelable property to be false. This stops the
user from being able to use the back button to dismiss the dialog.
2014-03-06 15:32:51 +10:30
Moxie Marlinspike
3d782449ed Merge pull request #1025 from DorianScholz/sharetextplain
Only receive text/plain.
2014-03-05 09:50:57 -08:00
Moxie Marlinspike
003095b08c Merge pull request #1020 from lxgr/fix-screensec
Use the "screen security" preference for the conversation list
2014-03-05 09:12:33 -08:00
Jake McGinty
d121d9531e Merge pull request #992 from veeti/overdraw
Fix overdraw issues in the conversation view
2014-03-05 00:10:06 -08:00
Veeti Paananen
42aeca26f1 Fix overdraw issues in the conversation view
Remove the redundant window background and row backgrounds to improve
drawing performance.
2014-03-05 08:03:55 +02:00
Dorian Scholz
ea8a1bae46 Only receive text/plain.
At the moment only text/plain is handled in RoutingActivity, so set filter in manifest accordingly.
This prevents #316. But at some point sharing text/calendar or better */* would be nice...

// FREEBIE
2014-03-04 23:57:36 +01:00
Jake McGinty
054fcdca8d Merge pull request #1007 from SamWhited/issue998
s/Verify Recipient/Verify Identity/
2014-03-04 13:49:49 -08:00
Lukas Ribisch
278220cf18 Use the "screen security" preference for the conversation list
It was previously only used for ConversationListActivity. This should be all
the locations (according to a grep for FLAG_SECURE).

FREEBIE
2014-03-04 21:04:04 +01:00
Chris Glass
35eeaa9bd5 Added more build information to BUILDING.md
Added more instructions about setting up a development environment.
2014-03-04 08:44:54 -08:00
Moxie Marlinspike
fbf02603ce Bump version to 2.0.4 2014-03-04 08:39:31 -08:00
Sam Whited
fa423e4432 s/Verify Recipient/Verify Identity/ 2014-03-04 09:55:45 -05:00
Moxie Marlinspike
5caec4a146 Updated language translations. 2014-03-03 19:13:38 -08:00
Moxie Marlinspike
dc77c43435 Merge pull request #796 from mcginty/pending_messages
pending messages more accurately conveyed to user
2014-03-03 18:49:48 -08:00
Jake McGinty
827298d1a2 Merge pull request #975 from tinloaf/reloadcontactlist
Reload contact list after directory refresh // FREEBIE
2014-03-03 18:01:10 -08:00
Lukas Barth
48f5b932f7 Reload contact list after directory refresh // FREEBIE
I forgot that in my PR, it seems.
2014-03-03 21:18:38 +01:00
Jake McGinty
90169e9468 pending messages more accurately conveyed to user 2014-03-03 11:56:54 -08:00
Moxie Marlinspike
b5fe378bc9 Workaround for Android bug when swiped from recent tasks list.
Closes #168

https://code.google.com/p/android/issues/detail?id=53313
2014-03-03 11:45:05 -08:00
Moxie Marlinspike
0f53c9d170 Merge pull request #966 from backspace/add-video-sharing
Support videos from other apps. Fixes #949
2014-03-03 11:01:02 -08:00
Moxie Marlinspike
3474950830 Merge pull request #956 from tinloaf/refreshfromlist
Add possiblity to refresh the push directory directly from contact list
2014-03-03 10:09:32 -08:00
Lukas Barth
39ee363150 Add possiblity to refresh the push directory directly from the contact picking list. Fixes #835. Fixes #859.
Thanks benalbrecht for the icons.
2014-03-03 18:23:14 +01:00
Buck Doyle
411e3ceff6 Support videos from other apps. Fixes #949 2014-03-03 11:55:34 -05:00
Moxie Marlinspike
b7f8c3b3d3 Merge pull request #961 from cloudkicker/notification_preferences
Make notification preferences look normally
2014-03-03 08:53:00 -08:00
Moxie Marlinspike
3efa8e6899 Merge pull request #955 from benalbrecht/nocount
Use new icon from #905 for push message disable confirmation box
2014-03-03 08:33:10 -08:00
Vladislav Meshochkin
948f481530 Make notification preferences look normally 2014-03-03 19:22:51 +04:00
Benjamin Albrecht
716fdefa4c Use icon from #905 for push message disable confirmation box
//FREEBIE
2014-03-03 11:44:14 +01:00
Moxie Marlinspike
2dc893730a Auto-update every 12 hours instead.
// FREEBIE
2014-03-02 16:19:05 -08:00
Moxie Marlinspike
1af99ce155 Fix for periodic directory refresh.
// FREEBIE
2014-03-02 16:14:00 -08:00
Moxie Marlinspike
0850f1b0f1 Add libcurve25519 bins.
// FREEBIE
2014-03-02 16:06:37 -08:00
Florian Walch
1ddc45fd9c Build native code for multiple architectures.
* Include native libs without requiring intermediate .jar.
 * Fix build warnings; use latest build tools.
2014-03-02 16:06:37 -08:00
Moxie Marlinspike
977765c80f Merge pull request #941 from Wikinaut/add-new-string-ViewIdentityActivity_my_identity_fingerprint
add new string ViewIdentityActivity_my_identity_fingerprint
2014-03-02 15:50:18 -08:00
Thomas Gries
4b2d07ab35 add new string ViewIdentityActivity_my_identity_fingerprint 2014-03-03 00:45:42 +01:00
Jens Füllenbach
003ebe6364 Added the color white to the notification colors of the LED. 2014-03-02 14:11:31 -08:00
Benjamin Albrecht
2f7c005c23 Show confirmation box before disabling push messages 2014-03-02 14:09:54 -08:00
Moxie Marlinspike
367b481d07 Merge pull request #905 from benalbrecht/dialog_icons
Use modern icons in info and alert dialogs for ICS+
2014-03-02 14:01:52 -08:00
Moxie Marlinspike
5cd8c922d2 Merge pull request #648 from veeti/tweaks-and-polish
Assorted tweaks and polish
2014-03-02 14:01:20 -08:00
Moxie Marlinspike
a2fe8a9d5c Make default group avatar icon actually work. 2014-03-02 13:22:02 -08:00
Benedict Pregler
ee921a8f49 fixed some hardcoded strings 2014-03-02 13:22:02 -08:00
Benedict Pregler
e0394b4481 a little typo mistake 2014-03-02 13:22:02 -08:00
Veeti Paananen
de100f5be7 Add missing spaces 2014-03-02 09:40:45 +02:00
Veeti Paananen
a020a57be6 End conversation selection action mode if all deselected
This seems to be common convention in most apps.
2014-03-02 09:40:35 +02:00
Veeti Paananen
28f1a0a636 Replace the attachment dialog header icon with something modern 2014-03-02 09:40:35 +02:00
Veeti Paananen
5a807ffc28 Use unified touch highlight in import & export 2014-03-02 09:40:35 +02:00
Veeti Paananen
757cb1c846 Add touch highlight to contact photos 2014-03-02 09:40:35 +02:00
Veeti Paananen
4f066757e7 Use the touch highlight background for the emoji toggle 2014-03-02 09:40:35 +02:00
Veeti Paananen
633aa9b057 Fix the send button's touch highlight color
Introduce a generic touch highlight background drawable, and use the
proper shades of blue (and gray on v19+).
2014-03-02 09:40:35 +02:00
Jake McGinty
a5c26b2e16 Merge pull request #921 from liliakai/master
Add contributing.md // FREEBIE
2014-03-01 15:19:32 -08:00
lilia
ca561d76ff Add contributing.md // FREEBIE
This file is recognized by github and presented to users who are trying
to open issues and submit pull requests.

Hopefully this more prominent placement will help deter people from
opening issues/prs for translation fixes.
2014-03-01 15:11:21 -08:00
Jake McGinty
107d999ee7 make bithub price update // FREEBIE
thanks lilia
2014-03-01 11:57:46 -08:00
Benjamin Albrecht
0f6c7660cb Use modern icons in info and alert dialogs for ICS+ 2014-03-01 14:42:12 +01:00
Moxie Marlinspike
8ea4db03db Better support for local storage with passphrase disabled.
1) Never get into a state where messages aren't displayed
   unencrypted.

2) Fix bug where locked DB required launching twice.
2014-02-28 15:58:30 -08:00
Moxie Marlinspike
81ee9e31c5 Update behavior for incoming SMS path.
1) On KitKat, unencrypted SMS messages are never stored in
   TextSecure unless it is set as the system-wide default.

2) On KitKat, if TextSecure is set as the system-wide default,
   provide an option to change the default to a different app.

3) Don't store the TextSecure challenge on KitKat+ devices.
2014-02-28 13:40:35 -08:00
Moxie Marlinspike
a3e900ecbe Merge pull request #794 from SamWhited/issue788
Add option to disable screen security
2014-02-27 18:02:05 -08:00
Sam Whited
384fb3b2b5 Add option to disable screen security
See WhisperSystems/TextSecure#788
2014-02-27 19:00:39 -05:00
Moxie Marlinspike
d795aa30b3 Merge pull request #795 from benalbrecht/nocount
Don't show letter count when SMS mode is disabled
2014-02-27 14:12:27 -08:00
Jake McGinty
bac4d63312 Merge pull request #805 from wickedshimmy/fix-sender-details
Don't display sender in outgoing message details
2014-02-27 11:18:51 -08:00
Benjamin Albrecht
bf60f90019 Don't show letter count inside a push group or when SMS mode is disabled 2014-02-27 09:32:19 +01:00
Matt Enright
852ca2ac05 Don't display sender in outgoing message details
Because we don't know it. Fixes WhisperSystems#711.
2014-02-26 21:57:17 -05:00
Moxie Marlinspike
038bebfdbb Bump version to 2.0.3 // FREEBIE 2014-02-26 13:08:03 -08:00
Moxie Marlinspike
3b25b87aa8 Fix for NPE 2014-02-26 12:42:16 -08:00
Moxie Marlinspike
5a62856e46 Make SMS delivery reports optional. 2014-02-26 12:38:50 -08:00
Moxie Marlinspike
37a52df4e6 Fix bug that broke notifications for group messages. 2014-02-26 12:31:56 -08:00
Jake McGinty
d3148b6766 Merge pull request #764 from m0jo/master
Fix hpyerlinking recieved/sent date in messages
2014-02-26 09:46:42 -08:00
Tim Bücher
6fb85aff6d Fix hpyerlinking recieved/sent date in messages 2014-02-26 13:43:55 +01:00
Moxie Marlinspike
ed45067227 Bumping version to 2.0.2 // FREEBIE 2014-02-26 00:01:52 -08:00
Jake McGinty
0015711759 migrate logcat submit to preferences
// FREEBIE
2014-02-25 23:11:00 -08:00
Moxie Marlinspike
15390e477e Update translations.
// FREEBIE
2014-02-25 22:47:03 -08:00
Jake McGinty
a8c23413ba Merge pull request #745 from mcginty/logcat_submit
activity to submit logcat to pastebin
2014-02-25 22:20:34 -08:00
Jake McGinty
ce68429a9b activity to submit logcat to pastebin 2014-02-25 22:14:21 -08:00
Jake McGinty
1a9a88a5a1 Merge pull request #744 from backspace/change-sent-link-color
Make outgoing links readable
2014-02-25 21:23:41 -08:00
Moxie Marlinspike
7987362c25 Fix for bug where messages are stuck in pending state after upgrade. 2014-02-25 20:57:18 -08:00
Buck Doyle
5d42110d6c Make outgoing links readable 2014-02-25 22:46:49 -05:00
Moxie Marlinspike
dec7fd4c8a Fix for NPE with audio attachments. 2014-02-25 17:54:49 -08:00
Moxie Marlinspike
82df23dd41 Temporarily bring back end session. 2014-02-25 17:38:55 -08:00
Moxie Marlinspike
ce710b378f Fix for stalled retries. 2014-02-25 17:15:30 -08:00
Moxie Marlinspike
20fd881613 Display error code from server when already registered elsewhere. 2014-02-25 17:00:31 -08:00
Jake McGinty
5fa429b0d5 Merge pull request #676 from mcginty/emoji_density
Fix emoji density scaling issues
2014-02-25 16:28:43 -08:00
Jake McGinty
630dce04fc handle emoji density scaling more correctly 2014-02-25 16:24:33 -08:00
Moxie Marlinspike
0da1d8818e Merge pull request #729 from funk78/feature/use_getDeviceE164Number
FREEBIE reuse code
2014-02-25 15:15:26 -08:00
droidastic
c84285c639 FREEBIE reuse code 2014-02-25 23:56:18 +01:00
Moxie Marlinspike
5a525a2e58 Switch KCS priority to MIN on JB+. Eliminates icon in status bar! 2014-02-25 10:33:11 -08:00
Moxie Marlinspike
dda8a214a4 Merge pull request #668 from m0jo/master
Fix missing localozation strings for custom LED blink pattern.
2014-02-25 08:37:38 -08:00
Tim Bücher
bd167cbb17 Fix missing localozation strings for custom LED blink pattern. 2014-02-25 11:38:31 +01:00
Jake McGinty
a0aaa7d724 Explain why SMS fallback is disabled when it is 2014-02-25 01:23:22 -08:00
557 changed files with 18626 additions and 9785 deletions

7
.travis.yml Normal file
View File

@@ -0,0 +1,7 @@
language: android
android:
components:
- platform-tools
- build-tools-19.1.0
- android-19
- extra-android-m2repository

View File

@@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,tr_TR:tr,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
[textsecure-official.master]

View File

@@ -2,10 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms"
android:versionCode="64"
android:versionName="2.0.1">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/>
android:versionCode="74"
android:versionName="2.1.2">
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
@@ -33,7 +31,6 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE"
@@ -45,6 +42,9 @@
android:label="@string/app_name"
android:theme="@style/TextSecure.LightTheme">
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity android:name=".RoutingActivity"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
@@ -69,8 +69,9 @@
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/*" />
</intent-filter>
<data android:mimeType="text/plain" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
@@ -89,14 +90,21 @@
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MmsPreferencesActivity"
<activity android:name=".MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
<activity android:name=".ShareActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:noHistory="true"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -118,15 +126,15 @@
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:theme="@style/TextSecure.IntroTheme"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphrasePromptActivity"
android:label="@string/AndroidManifest__enter_passphrase"
android:launchMode="singleTop"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/TextSecure.IntroTheme"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ContactSelectionActivity"
@@ -134,15 +142,15 @@
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".SingleContactSelectionActivity"
android:label="@string/AndroidManifest__select_contact"
<activity android:name=".NewConversationActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".AutoInitiateActivity"
android:theme="@style/TextSecure.Light.Dialog"
@@ -164,11 +172,7 @@
android:label="@string/AndroidManifest__verify_identity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ReviewIdentitiesActivity"
android:label="@string/AndroidManifest__manage_identity_keys"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ReceiveKeyActivity"
<activity android:name=".ReceiveKeyActivity"
android:label="@string/AndroidManifest__complete_key_exchange"
android:theme="@style/TextSecure.Light.Dialog"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -183,11 +187,29 @@
<activity android:name=".RegistrationProgressActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".LogSubmitActivity"
android:label="@string/AndroidManifest__log_submit"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
android:enabled="true"
android:allowTaskReparenting="true"
android:noHistory="true"
android:excludeFromRecents="true"
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
<service android:enabled="true" android:name=".service.GcmRegistrationService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.SendReceiveService"/>
<service android:enabled="true" android:name=".service.RegistrationService"/>
<service android:enabled="true" android:name=".gcm.GcmIntentService"/>
<service android:enabled="true" android:name=".service.DirectoryRefreshService"/>
<service android:enabled="true" android:name=".service.PreKeyService"/>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
@@ -214,7 +236,6 @@
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="org.thoughtcrime.securesms" />
</intent-filter>
</receiver>
@@ -280,8 +301,4 @@
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="org.thoughtcrime.securesms.tests" android:label="Tests for My App" />
</manifest>

View File

@@ -1,8 +1,67 @@
Building TextSecure
===================
=====================
1. Ensure the 'Android Support Repository' is installed from the Android SDK manager.
Basics
------
Execute Gradle:
TextSecure uses [Gradle](http://gradle.org) to build the project and to maintain
dependencies.
./gradlew build
Building TextSecure
-------------------
The following steps should help you (re)build TextSecure from the command line.
1. Checkout the source somewhere on your filesystem with
git clone https://github.com/WhisperSystems/TextSecure.git
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed somewhere on your system.
3. Ensure the "Android Support Repository" and "Android SDK Build-tools" are installed from the Android SDK manager.
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it.
sdk.dir=\<path to your sdk installation\>
5. Execute Gradle:
./gradlew build
Re-building native components
-----------------------------
Note: This step is optional; native components are contained as binaries (see [library/libs](library/libs)).
1. Ensure that the Android NDK is installed.
Execute ndk-build:
cd library
ndk-build
Afterwards, execute Gradle as above to re-create the APK.
Setting up a development environment
------------------------------------
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
1. Install Android Studio.
2. Make sure the "Android Support Repository" is installed in the Android Studio SDK.
3. Make sure the latest "Android SDK build-tools" is installed in the Android Studio SDK.
4. Create a new Android Studio project. from the Quickstart pannel (use File > Close Project to see it), choose "Checkout from Version Control" then "git".
5. Paste the URL for the TextSecure project when prompted (https://github.com/WhisperSystems/TextSecure.git).
6. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
7. Default config options should be good enough.
8. Project initialisation and build should proceed.
Contributing code
-----------------
Code contributions should be sent via github as pull requests, from feature branches [as explained here](https://help.github.com/articles/using-pull-requests).
Mailing list
------------
Development discussion happens on the whispersystems mailing list.
[To join](https://lists.riseup.net/www/info/whispersystems)
Send emails to whispersystems@lists.riseup.net

View File

@@ -1,70 +1,73 @@
TextSecure
=================
# TextSecure [![Build Status](https://travis-ci.org/WhisperSystems/TextSecure.svg?branch=master)](https://travis-ci.org/WhisperSystems/TextSecure)
A secure text messaging application for Android.
TextSecure is a messaging app for easy private communicate with friends.
TextSecure is a replacement for the standard text messaging application, allowing you to send and receive text messages as normal. Additionally, TextSecure provides:
TextSecure can use either data (WiFi/3G/4G) or SMS to communicate securely, and all TextSecure
messages can also be encrypted locally on your device.
1. *Local Encryption* -- All text messages, regardless of destination, that are sent or received with TextSecure are stored in an encrypted database on your phone.
2. *Wire Encryption* -- When communicating with a recipient who is also using TextSecure, text messages are encrypted during transmission.
Currently available on the Play store.
Current BitHub Payment For Commit:
=================
[![Current Price](https://bithub.herokuapp.com/v1/status/payment/commit)](https://whispersystems.org/blog/bithub/)
*[![Play Store Badge](https://developer.android.com/images/brand/en_app_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms)*
Bug tracker
-----------
Have a bug? Please create an issue here on GitHub!
## Contributing Bug reports
We use GitHub for bug tracking. Please search the existing issues for your bug and create a new one if the issue is not yet tracked!
https://github.com/WhisperSystems/TextSecure/issues
## Contributing Translations
Interested in helping to translate TextSecure? Contribute here:
Documentation
-------------
https://www.transifex.com/projects/p/textsecure-official/
Looking for documentation? Check out the wiki!
## Contributing Code
Instructions on how to setup your development environment and build TextSecure can be found in [BUILDING.md](https://github.com/WhisperSystems/TextSecure/blob/master/BUILDING.md).
https://github.com/WhisperSystems/TextSecure/wiki
If you're new to the TextSecure codebase, we recommend going through our issues and picking out a simple bug to fix (check the "easy" label in our issues) in order to get yourself familiar.
Mailing list
------------
For larger changes and feature ideas, we ask that you propose it on the mailing list for a high-level discussion before implementation.
Have a question? Ask on our mailing list!
This repository is set up with [BitHub](https://whispersystems.org/blog/bithub/), so you can make money for committing to TextSecure. The current BitHub price for an accepted pull request is:
[![Current BitHub Price](https://bithub.herokuapp.com/v1/status/payment/commit/)](https://whispersystems.org/blog/bithub/)
## Contributing Ideas
Have something you want to say about Open Whisper Systems projects or want to be part of the conversation? Get involved in the mailing list!
whispersystems@lists.riseup.net
https://lists.riseup.net/www/info/whispersystems
Translation
------------
## Contributing Funds
[![Donate](https://coinbase.com/assets/buttons/donation_large-6ec72b1a9eec516944e50a22aca7db35.png)](https://whispersystems.org/blog/bithub/)
Interested in helping to translate TextSecure? Contribute here:
You can add funds to BitHub to directly help further development efforts.
https://www.transifex.com/projects/p/textsecure-official/
Help
====
## Support
For troubleshooting and questions, please visit our support center!
Downloads
------------
http://support.whispersystems.org/
TextSecure can be downloaded from the Play Store here:
## Documentation
Looking for documentation? Check out the wiki!
https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms
https://github.com/WhisperSystems/TextSecure/wiki
Cryptography Notice
------------
# Legal things
## Cryptography Notice
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted.
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted.
See <http://www.wassenaar.org/> for more information.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms.
The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code.
License
---------------------
## License
Copyright 2011 Whisper Systems
Copyright 2013 Open WhisperSystems
Copyright 2013-2014 Open Whisper Systems
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html

View File

@@ -0,0 +1,118 @@
package org.thoughtcrime.securesms.database;
import android.test.InstrumentationTestCase;
import static org.fest.assertions.api.Assertions.assertThat;
public class CanonicalAddressDatabaseTest extends InstrumentationTestCase {
private static final String AMBIGUOUS_NUMBER = "222-3333";
private static final String SPECIFIC_NUMBER = "+49 444 222 3333";
private static final String EMAIL = "a@b.fom";
private static final String SIMILAR_EMAIL = "a@b.com";
private static final String GROUP = "__textsecure_group__!000111222333";
private static final String SIMILAR_GROUP = "__textsecure_group__!100111222333";
private static final String ALPHA = "T-Mobile";
private static final String SIMILAR_ALPHA = "T-Mobila";
private CanonicalAddressDatabase db;
public void setUp() throws Exception {
super.setUp();
this.db = CanonicalAddressDatabase.getInstance(getInstrumentation().getTargetContext());
}
public void tearDown() throws Exception {
}
/**
* Throw two equivalent numbers (one without locale info, one with full info) at the canonical
* address db and see that the caching and DB operations work properly in revealing the right
* addresses. This is run twice to ensure cache logic is hit.
*
* @throws Exception
*/
public void testNumberAddressUpdates() throws Exception {
final long id = db.getCanonicalAddressId(AMBIGUOUS_NUMBER);
assertThat(db.getAddressFromId(id)).isEqualTo(AMBIGUOUS_NUMBER);
assertThat(db.getCanonicalAddressId(SPECIFIC_NUMBER)).isEqualTo(id);
assertThat(db.getAddressFromId(id)).isEqualTo(SPECIFIC_NUMBER);
assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
assertThat(db.getAddressFromId(id)).isEqualTo(AMBIGUOUS_NUMBER);
assertThat(db.getCanonicalAddressId(SPECIFIC_NUMBER)).isEqualTo(id);
assertThat(db.getAddressFromId(id)).isEqualTo(SPECIFIC_NUMBER);
assertThat(db.getCanonicalAddressId(AMBIGUOUS_NUMBER)).isEqualTo(id);
}
public void testSimilarNumbers() throws Exception {
assertThat(db.getCanonicalAddressId("This is a phone number 222-333-444"))
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("222-333-444"))
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("222-333-44"))
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("222-333-4"))
.isNotEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("+49 222-333-4444"))
.isNotEqualTo(db.getCanonicalAddressId("+1 222-333-4444"));
assertThat(db.getCanonicalAddressId("1 222-333-4444"))
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("1 (222) 333-4444"))
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("+12223334444"))
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("+1 (222) 333.4444"))
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
assertThat(db.getCanonicalAddressId("+49 (222) 333.4444"))
.isEqualTo(db.getCanonicalAddressId("222-333-4444"));
}
public void testEmailAddresses() throws Exception {
final long emailId = db.getCanonicalAddressId(EMAIL);
final long similarEmailId = db.getCanonicalAddressId(SIMILAR_EMAIL);
assertThat(emailId).isNotEqualTo(similarEmailId);
assertThat(db.getAddressFromId(emailId)).isEqualTo(EMAIL);
assertThat(db.getAddressFromId(similarEmailId)).isEqualTo(SIMILAR_EMAIL);
}
public void testGroups() throws Exception {
final long groupId = db.getCanonicalAddressId(GROUP);
final long similarGroupId = db.getCanonicalAddressId(SIMILAR_GROUP);
assertThat(groupId).isNotEqualTo(similarGroupId);
assertThat(db.getAddressFromId(groupId)).isEqualTo(GROUP);
assertThat(db.getAddressFromId(similarGroupId)).isEqualTo(SIMILAR_GROUP);
}
public void testAlpha() throws Exception {
final long id = db.getCanonicalAddressId(ALPHA);
final long similarId = db.getCanonicalAddressId(SIMILAR_ALPHA);
assertThat(id).isNotEqualTo(similarId);
assertThat(db.getAddressFromId(id)).isEqualTo(ALPHA);
assertThat(db.getAddressFromId(similarId)).isEqualTo(SIMILAR_ALPHA);
}
public void testIsNumber() throws Exception {
assertThat(CanonicalAddressDatabase.isNumberAddress("+495556666777")).isTrue();
assertThat(CanonicalAddressDatabase.isNumberAddress("(222) 333-4444")).isTrue();
assertThat(CanonicalAddressDatabase.isNumberAddress("1 (222) 333-4444")).isTrue();
assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile123")).isTrue();
assertThat(CanonicalAddressDatabase.isNumberAddress("333-4444")).isTrue();
assertThat(CanonicalAddressDatabase.isNumberAddress("12345")).isTrue();
assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile")).isFalse();
assertThat(CanonicalAddressDatabase.isNumberAddress("T-Mobile1")).isFalse();
assertThat(CanonicalAddressDatabase.isNumberAddress("Wherever bank")).isFalse();
assertThat(CanonicalAddressDatabase.isNumberAddress("__textsecure_group__!afafafafafaf")).isFalse();
assertThat(CanonicalAddressDatabase.isNumberAddress("email@domain.com")).isFalse();
}
}

View File

@@ -0,0 +1,33 @@
package org.thoughtcrime.securesms.util;
import android.test.AndroidTestCase;
import junit.framework.AssertionFailedError;
import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import static org.fest.assertions.api.Assertions.assertThat;
public class PhoneNumberFormatterTest extends AndroidTestCase {
private static final String LOCAL_NUMBER = "+15555555555";
public void testFormatNumberE164() throws Exception, InvalidNumberException {
assertThat(PhoneNumberFormatter.formatNumber("(555) 555-5555", LOCAL_NUMBER)).isEqualTo(LOCAL_NUMBER);
assertThat(PhoneNumberFormatter.formatNumber("555-5555", LOCAL_NUMBER)).isEqualTo(LOCAL_NUMBER);
assertThat(PhoneNumberFormatter.formatNumber("(123) 555-5555", LOCAL_NUMBER)).isNotEqualTo(LOCAL_NUMBER);
}
public void testFormatNumberEmail() throws Exception {
try {
PhoneNumberFormatter.formatNumber("person@domain.com", LOCAL_NUMBER);
throw new AssertionFailedError("should have thrown on email");
} catch (InvalidNumberException ine) {
// success
}
}
@Override
public void setUp() throws Exception {
super.setUp();
}
}

View File

@@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.util;
import android.test.AndroidTestCase;
import static org.fest.assertions.api.Assertions.assertThat;
public class UtilTest extends AndroidTestCase {
}

BIN
artwork/ic_send.psd Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

BIN
assets/emoji_0_wrapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

BIN
assets/emoji_1_wrapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

BIN
assets/emoji_2_wrapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

BIN
assets/emoji_3_wrapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

BIN
assets/emoji_4_wrapped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

View File

@@ -3,11 +3,13 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.+'
classpath 'com.android.tools.build:gradle:0.12.+'
classpath files('libs/gradle-witness.jar')
}
}
apply plugin: 'android'
apply plugin: 'com.android.application'
apply plugin: 'witness'
repositories {
mavenCentral()
@@ -21,15 +23,32 @@ repositories {
dependencies {
compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
compile 'com.android.support:support-v4:19.0.1'
compile 'com.google.android.gcm:gcm-client:1.0.2'
compile 'com.android.support:support-v4:19.1.0'
compile 'se.emilsjolander:stickylistheaders:2.2.0'
compile 'com.google.android.gms:play-services:5.0.77'
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
androidTestCompile 'com.squareup:fest-android:1.0.8'
compile project(':library')
}
dependencyVerification {
verify = [
'com.actionbarsherlock:actionbarsherlock:5ab04d74101f70024b222e3ff9c87bee151ec43331b4a2134b6cc08cf8565819',
'com.android.support:support-v4:3f40fa7b3a4ead01ce15dce9453b061646e7fe2e7c51cb75ca01ee1e77037f3f',
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
'com.google.protobuf:protobuf-java:ad9769a22989e688a46af4d3accc348cc501ced22118033230542bc916e33f0b',
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
]
}
android {
compileSdkVersion 19
buildToolsVersion '19.0.0'
buildToolsVersion '19.1.0'
defaultConfig {
minSdkVersion 9
@@ -42,11 +61,17 @@ android {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aild.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest {
java.srcDirs = ['androidTest']
resources.srcDirs = ['androidTest']
aidl.srcDirs = ['androidTest']
renderscript.srcDirs = ['androidTest']
}
}
}

30
contributing.md Normal file
View File

@@ -0,0 +1,30 @@
##Translations
Please do not submit issues or pull requests for translation fixes. Anyone can update the translations in [Transifex](https://www.transifex.com/projects/p/textsecure-official/). Please submit your corrections there.
## Submitting useful bug reports
1. Search our issues first to make sure this is not a duplicate.
1. Read the [Submitting useful bug reports guide](https://github.com/WhisperSystems/TextSecure/wiki/Submitting-useful-bug-reports) before posting a bug.
## Development Ideology
Truths which we believe to be self-evident:
1. **The answer is not more options.** If you feel compelled to add a
preference that's exposed to the user, it's very possible you've made
a wrong turn somewhere.
1. **The user doesn't know what a key is.** We need to minimize the points
at which a user is exposed to this sort of terminology as extremely as
possible.
1. **There are no power users.** The idea that some users "understand"
concepts better than others has proven to be, for the most part, false.
If anything, "power users" are more dangerous than the rest, and we
should avoid exposing dangerous functionality to them.
1. **If it's "like PGP," it's wrong.** PGP is our spirit guide for what
not to do.
1. **It's an asynchronous world.** Be wary of anything that is
anti-asynchronous: ACKs, protocol confirmations, or any protocol-level
"advisory" message.
1. **There is no such thing as time.** Protocol ideas that require synchronized
clocks are doomed to failure.

View File

@@ -1,6 +1,6 @@
#Sat Dec 21 23:48:05 PST 2013
#Mon Jun 09 23:26:49 PDT 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip

View File

@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.+'
classpath 'com.android.tools.build:gradle:0.12.+'
}
}
@@ -21,14 +21,13 @@ repositories {
dependencies {
compile 'com.google.protobuf:protobuf-java:2.4.1'
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
compile 'com.googlecode.libphonenumber:libphonenumber:5.3'
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
compile 'org.whispersystems:gson:2.2.4'
compile fileTree(dir: 'libs', include: 'armeabi.jar')
}
android {
compileSdkVersion 19
buildToolsVersion '19.0.0'
buildToolsVersion '19.1.0'
android {
sourceSets {
@@ -36,10 +35,11 @@ android {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aild.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
}

View File

@@ -0,0 +1 @@
APP_ABI := armeabi armeabi-v7a x86

View File

@@ -22,11 +22,16 @@
#include "curve25519-donna.h"
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePrivateKey
(JNIEnv *env, jclass clazz, jbyteArray random)
(JNIEnv *env, jclass clazz, jbyteArray random, jboolean ephemeral)
{
uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
privateKey[0] &= 248;
if (ephemeral) {
privateKey[0] |= 1;
}
privateKey[31] &= 127;
privateKey[31] |= 64;

View File

@@ -720,9 +720,6 @@ curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
int i;
for (i = 0; i < 32; ++i) e[i] = secret[i];
e[0] &= 248;
e[31] &= 127;
e[31] |= 64;
fexpand(bp, basepoint);
cmult(x, z, e, bp);

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
library/libs/x86/libcurve25519.so Executable file

Binary file not shown.

View File

@@ -54,6 +54,13 @@ message SessionStructure {
optional uint32 remoteRegistrationId = 10;
optional uint32 localRegistrationId = 11;
optional bool needsRefresh = 12;
}
message RecordStructure {
optional SessionStructure currentSession = 1;
repeated SessionStructure previousSessions = 2;
}
message PreKeyRecordStructure {

View File

@@ -0,0 +1,7 @@
package org.whispersystems.textsecure.crypto;
public class DuplicateMessageException extends Exception {
public DuplicateMessageException(String s) {
super(s);
}
}

View File

@@ -23,7 +23,6 @@ import android.os.Parcelable;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
/**
* A class for representing an identity key.
@@ -80,14 +79,7 @@ public class IdentityKey implements Parcelable, SerializableKey {
}
public byte[] serialize() {
if (publicKey.getType() == Curve.NIST_TYPE) {
byte[] versionBytes = {0x01};
byte[] encodedKey = publicKey.serialize();
return Util.combine(versionBytes, encodedKey);
} else {
return publicKey.serialize();
}
return publicKey.serialize();
}
public String getFingerprint() {

View File

@@ -1,81 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
/**
* Represents a session's active KeyPair.
*
* @author Moxie Marlinspike
*/
public class KeyPair {
private PublicKey publicKey;
private ECPrivateKey privateKey;
private final MasterCipher masterCipher;
public KeyPair(int keyPairId, ECKeyPair keyPair, MasterSecret masterSecret) {
this.masterCipher = new MasterCipher(masterSecret);
this.publicKey = new PublicKey(keyPairId, keyPair.getPublicKey());
this.privateKey = keyPair.getPrivateKey();
}
public KeyPair(byte[] bytes, MasterCipher masterCipher) throws InvalidKeyException {
this.masterCipher = masterCipher;
deserialize(bytes);
}
public int getId() {
return publicKey.getId();
}
public PublicKey getPublicKey() {
return publicKey;
}
public ECPrivateKey getPrivateKey() {
return privateKey;
}
public byte[] toBytes() {
return serialize();
}
private void deserialize(byte[] bytes) throws InvalidKeyException {
this.publicKey = new PublicKey(bytes);
byte[] privateKeyBytes = new byte[bytes.length - PublicKey.KEY_SIZE];
System.arraycopy(bytes, PublicKey.KEY_SIZE, privateKeyBytes, 0, privateKeyBytes.length);
this.privateKey = masterCipher.decryptKey(this.publicKey.getType(), privateKeyBytes);
}
public byte[] serialize() {
byte[] publicKeyBytes = publicKey.serialize();
Log.w("KeyPair", "Serialized public key bytes: " + Hex.toString(publicKeyBytes));
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
return Util.combine(publicKeyBytes, privateKeyBytes);
}
}

View File

@@ -0,0 +1,7 @@
package org.whispersystems.textsecure.crypto;
public class LegacyMessageException extends Exception {
public LegacyMessageException(String s) {
super(s);
}
}

View File

@@ -83,11 +83,11 @@ public class MasterCipher {
return new String(decodeAndDecryptBytes(body));
}
public ECPrivateKey decryptKey(int type, byte[] key)
public ECPrivateKey decryptKey(byte[] key)
throws org.whispersystems.textsecure.crypto.InvalidKeyException
{
try {
return Curve.decodePrivatePoint(type, decryptBytes(key));
return Curve.decodePrivatePoint(decryptBytes(key));
} catch (InvalidMessageException ime) {
throw new org.whispersystems.textsecure.crypto.InvalidKeyException(ime);
}

View File

@@ -34,7 +34,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
@@ -49,7 +48,7 @@ public class PreKeyUtil {
for (int i=0;i<BATCH_SIZE;i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
ECKeyPair keyPair = Curve25519.generateKeyPair();
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
record.save();
@@ -70,7 +69,7 @@ public class PreKeyUtil {
}
}
ECKeyPair keyPair = Curve25519.generateKeyPair();
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
record.save();
@@ -78,38 +77,38 @@ public class PreKeyUtil {
return record;
}
public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
File directory = getPreKeysDirectory(context);
String[] keyRecordIds = directory.list();
Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
for (String keyRecordId : keyRecordIds) {
try {
if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
}
} catch (InvalidKeyIdException e) {
Log.w("PreKeyUtil", e);
new File(getPreKeysDirectory(context), keyRecordId).delete();
} catch (NumberFormatException nfe) {
Log.w("PreKeyUtil", nfe);
new File(getPreKeysDirectory(context), keyRecordId).delete();
}
}
return records;
}
public static void clearPreKeys(Context context) {
File directory = getPreKeysDirectory(context);
String[] keyRecords = directory.list();
for (String keyRecord : keyRecords) {
new File(directory, keyRecord).delete();
}
}
// public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
// List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
// File directory = getPreKeysDirectory(context);
// String[] keyRecordIds = directory.list();
//
// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
//
// for (String keyRecordId : keyRecordIds) {
// try {
// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
// }
// } catch (InvalidKeyIdException e) {
// Log.w("PreKeyUtil", e);
// new File(getPreKeysDirectory(context), keyRecordId).delete();
// } catch (NumberFormatException nfe) {
// Log.w("PreKeyUtil", nfe);
// new File(getPreKeysDirectory(context), keyRecordId).delete();
// }
// }
//
// return records;
// }
//
// public static void clearPreKeys(Context context) {
// File directory = getPreKeysDirectory(context);
// String[] keyRecords = directory.list();
//
// for (String keyRecord : keyRecords) {
// new File(directory, keyRecord).delete();
// }
// }
private static void setNextPreKeyId(Context context, int id) {
try {
@@ -126,7 +125,7 @@ public class PreKeyUtil {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
if (nextFile.exists()) {
if (!nextFile.exists()) {
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));

View File

@@ -18,31 +18,258 @@ package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.crypto.protocol.WhisperMessage;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV1;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import org.whispersystems.textsecure.util.Conversions;
public abstract class SessionCipher {
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
protected static final Object SESSION_LOCK = new Object();
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public abstract CiphertextMessage encrypt(byte[] paddedMessage);
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException;
public abstract int getRemoteRegistrationId();
public class SessionCipher {
private static final Object SESSION_LOCK = new Object();
private final Context context;
private final MasterSecret masterSecret;
private final RecipientDevice recipient;
public static SessionCipher createFor(Context context,
MasterSecret masterSecret,
RecipientDevice recipient)
{
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
return new SessionCipherV2(context, masterSecret, recipient);
} else if (SessionRecordV1.hasSession(context, recipient.getRecipientId())) {
return new SessionCipherV1(context, masterSecret, recipient.getRecipient());
return new SessionCipher(context, masterSecret, recipient);
} else {
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
}
}
private SessionCipher(Context context, MasterSecret masterSecret, RecipientDevice recipient) {
this.recipient = recipient;
this.masterSecret = masterSecret;
this.context = context;
}
public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
ChainKey chainKey = sessionState.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
int previousCounter = sessionState.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessage(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody);
if (sessionState.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
int localRegistrationId = sessionState.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
pendingPreKey.second,
sessionState.getLocalIdentityKey(),
(WhisperMessage) ciphertextMessage);
}
sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionRecord.save();
return ciphertextMessage;
}
}
public byte[] decrypt(byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionRecord.save();
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
for (SessionState previousState : previousStates) {
try {
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionRecord.save();
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
}
throw new InvalidMessageException("No valid sessions.");
}
}
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionState.clearPendingPreKey();
return plaintext;
}
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
return sessionRecord.getSessionState().getRemoteRegistrationId();
}
}
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
throws InvalidMessageException
{
try {
if (sessionState.hasReceiverChain(theirEphemeral)) {
return sessionState.getReceiverChainKey(theirEphemeral);
} else {
RootKey rootKey = sessionState.getRootKey();
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionState.setRootKey(senderChain.first);
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
return receiverChain.second;
}
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
ECPublicKey theirEphemeral,
ChainKey chainKey, int counter)
throws InvalidMessageException, DuplicateMessageException
{
if (chainKey.getIndex() > counter) {
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
return sessionState.removeMessageKeys(theirEphemeral, counter);
} else {
throw new DuplicateMessageException("Received message with old counter: " +
chainKey.getIndex() + " , " + counter);
}
}
if (chainKey.getIndex() - counter > 2000) {
throw new InvalidMessageException("Over 2000 messages into the future!");
}
while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys();
sessionState.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey();
}
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
return chainKey.getMessageKeys();
}
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(plaintext);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] ivBytes = new byte[16];
Conversions.intToByteArray(ivBytes, 0, counter);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(mode, key, iv);
return cipher;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (java.security.InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
}
}
private SessionRecordV2 getSessionRecord() {
return new SessionRecordV2(context, masterSecret, recipient);
}
}

View File

@@ -1,332 +0,0 @@
package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.NKDF;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV1;
import org.whispersystems.textsecure.storage.CanonicalRecipient;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionKey;
import org.whispersystems.textsecure.storage.SessionRecordV1;
import org.whispersystems.textsecure.util.Conversions;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class SessionCipherV1 extends SessionCipher {
private final Context context;
private final MasterSecret masterSecret;
private final CanonicalRecipient recipient;
public SessionCipherV1(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)
{
this.context = context;
this.masterSecret = masterSecret;
this.recipient = recipient;
}
public CiphertextMessage encrypt(byte[] paddedMessageBody) {
synchronized (SESSION_LOCK) {
SessionCipherContext encryptionContext = getEncryptionContext();
byte[] cipherText = getCiphertext(paddedMessageBody,
encryptionContext.getSessionKey().getCipherKey(),
encryptionContext.getSessionRecord().getCounter());
encryptionContext.getSessionRecord().setSessionKey(encryptionContext.getSessionKey());
encryptionContext.getSessionRecord().incrementCounter();
encryptionContext.getSessionRecord().save();
return new WhisperMessageV1(encryptionContext, cipherText);
}
}
public byte[] decrypt(byte[] decodedCiphertext) throws InvalidMessageException {
synchronized (SESSION_LOCK) {
WhisperMessageV1 message = new WhisperMessageV1(decodedCiphertext);
SessionCipherContext decryptionContext = getDecryptionContext(message);
message.verifyMac(decryptionContext);
byte[] plaintextWithPadding = getPlaintext(message.getBody(),
decryptionContext.getSessionKey().getCipherKey(),
decryptionContext.getCounter());
decryptionContext.getRemoteKeyRecord().updateCurrentRemoteKey(decryptionContext.getNextKey());
decryptionContext.getRemoteKeyRecord().save();
decryptionContext.getLocalKeyRecord().advanceKeyIfNecessary(decryptionContext.getRecipientKeyId());
decryptionContext.getLocalKeyRecord().save();
decryptionContext.getSessionRecord().setSessionKey(decryptionContext.getSessionKey());
decryptionContext.getSessionRecord().save();
return plaintextWithPadding;
}
}
@Override
public int getRemoteRegistrationId() {
return 0;
}
private SessionCipherContext getEncryptionContext() {
try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
int sessionVersion = records.getSessionRecord().getSessionVersion();
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE,
records, localKeyId, remoteKeyId);
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
int counter = records.getSessionRecord().getCounter();
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
nextKey, counter, sessionVersion);
} catch (InvalidKeyIdException e) {
throw new IllegalArgumentException(e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(e);
}
}
public SessionCipherContext getDecryptionContext(WhisperMessageV1 message)
throws InvalidMessageException
{
try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
int messageVersion = message.getCurrentVersion();
int recipientKeyId = message.getReceiverKeyId();
int senderKeyId = message.getSenderKeyId();
PublicKey nextKey = new PublicKey(message.getNextKeyBytes());
int counter = message.getCounter();
if (messageVersion < records.getSessionRecord().getSessionVersion()) {
throw new InvalidMessageException("Message version: " + messageVersion +
" but negotiated session version: " +
records.getSessionRecord().getSessionVersion());
}
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE,
records, recipientKeyId, senderKeyId);
return new SessionCipherContext(records, sessionKey, senderKeyId,
recipientKeyId, nextKey, counter,
messageVersion);
} catch (InvalidKeyIdException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) {
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter);
return cipher.doFinal(message);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) {
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter);
return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] ivBytes = new byte[16];
Conversions.mediumToByteArray(ivBytes, 0, counter);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(mode, key, iv);
return cipher;
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("AES Not Supported!");
} catch (NoSuchPaddingException e) {
throw new IllegalArgumentException("NoPadding Not Supported!");
} catch (java.security.InvalidKeyException e) {
Log.w("SessionCipher", e);
throw new IllegalArgumentException("Invaid Key?");
} catch (InvalidAlgorithmParameterException e) {
Log.w("SessionCipher", e);
throw new IllegalArgumentException("Bad IV?");
}
}
private SessionKey getSessionKey(MasterSecret masterSecret, int mode,
KeyRecords records,
int localKeyId, int remoteKeyId)
throws InvalidKeyIdException, InvalidKeyException
{
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
SessionKey sessionKey = records.getSessionRecord().getSessionKey(mode, localKeyId, remoteKeyId);
if (sessionKey != null)
return sessionKey;
DerivedSecrets derivedSecrets = calculateSharedSecret(mode, records, localKeyId, remoteKeyId);
return new SessionKey(mode, localKeyId, remoteKeyId, derivedSecrets.getCipherKey(),
derivedSecrets.getMacKey(), masterSecret);
}
private DerivedSecrets calculateSharedSecret(int mode, KeyRecords records,
int localKeyId, int remoteKeyId)
throws InvalidKeyIdException, InvalidKeyException
{
NKDF kdf = new NKDF();
KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
ECPublicKey remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
byte[] sharedSecret = Curve.calculateAgreement(remoteKey, localKeyPair.getPrivateKey());
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd);
return kdf.deriveSecrets(sharedSecret, isLowEnd);
}
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
{
ECPublicKey localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
ECPublicKey remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
return localPublic.compareTo(remotePublic) < 0;
}
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
CanonicalRecipient recipient)
{
LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient);
SessionRecordV1 sessionRecord = new SessionRecordV1(context, masterSecret, recipient);
return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord);
}
private static class KeyRecords {
private final LocalKeyRecord localKeyRecord;
private final RemoteKeyRecord remoteKeyRecord;
private final SessionRecordV1 sessionRecord;
public KeyRecords(LocalKeyRecord localKeyRecord,
RemoteKeyRecord remoteKeyRecord,
SessionRecordV1 sessionRecord)
{
this.localKeyRecord = localKeyRecord;
this.remoteKeyRecord = remoteKeyRecord;
this.sessionRecord = sessionRecord;
}
private LocalKeyRecord getLocalKeyRecord() {
return localKeyRecord;
}
private RemoteKeyRecord getRemoteKeyRecord() {
return remoteKeyRecord;
}
private SessionRecordV1 getSessionRecord() {
return sessionRecord;
}
}
public static class SessionCipherContext {
private final LocalKeyRecord localKeyRecord;
private final RemoteKeyRecord remoteKeyRecord;
private final SessionRecordV1 sessionRecord;
private final SessionKey sessionKey;
private final int senderKeyId;
private final int recipientKeyId;
private final PublicKey nextKey;
private final int counter;
private final int messageVersion;
public SessionCipherContext(KeyRecords records,
SessionKey sessionKey,
int senderKeyId,
int receiverKeyId,
PublicKey nextKey,
int counter,
int messageVersion)
{
this.localKeyRecord = records.getLocalKeyRecord();
this.remoteKeyRecord = records.getRemoteKeyRecord();
this.sessionRecord = records.getSessionRecord();
this.sessionKey = sessionKey;
this.senderKeyId = senderKeyId;
this.recipientKeyId = receiverKeyId;
this.nextKey = nextKey;
this.counter = counter;
this.messageVersion = messageVersion;
}
public LocalKeyRecord getLocalKeyRecord() {
return localKeyRecord;
}
public RemoteKeyRecord getRemoteKeyRecord() {
return remoteKeyRecord;
}
public SessionRecordV1 getSessionRecord() {
return sessionRecord;
}
public SessionKey getSessionKey() {
return sessionKey;
}
public PublicKey getNextKey() {
return nextKey;
}
public int getCounter() {
return counter;
}
public int getSenderKeyId() {
return senderKeyId;
}
public int getRecipientKeyId() {
return recipientKeyId;
}
public int getMessageVersion() {
return messageVersion;
}
}
}

View File

@@ -1,211 +0,0 @@
package org.whispersystems.textsecure.crypto;
import android.content.Context;
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.WhisperMessageV2;
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.util.Conversions;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class SessionCipherV2 extends SessionCipher {
private final Context context;
private final MasterSecret masterSecret;
private final RecipientDevice recipient;
public SessionCipherV2(Context context,
MasterSecret masterSecret,
RecipientDevice recipient)
{
this.context = context;
this.masterSecret = masterSecret;
this.recipient = recipient;
}
@Override
public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
ChainKey chainKey = sessionRecord.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionRecord.getSenderEphemeral();
int previousCounter = sessionRecord.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody);
if (sessionRecord.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionRecord.getPendingPreKey();
int localRegistrationId = sessionRecord.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
pendingPreKey.second,
sessionRecord.getLocalIdentityKey(),
(WhisperMessageV2) ciphertextMessage);
}
sessionRecord.setSenderChainKey(chainKey.getNextChainKey());
sessionRecord.save();
return ciphertextMessage;
}
}
@Override
public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionRecord, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionRecord, theirEphemeral,
chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionRecord.clearPendingPreKey();
sessionRecord.save();
return plaintext;
}
}
@Override
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
return sessionRecord.getRemoteRegistrationId();
}
}
private ChainKey getOrCreateChainKey(SessionRecordV2 sessionRecord, ECPublicKey theirEphemeral)
throws InvalidMessageException
{
try {
if (sessionRecord.hasReceiverChain(theirEphemeral)) {
return sessionRecord.getReceiverChainKey(theirEphemeral);
} else {
RootKey rootKey = sessionRecord.getRootKey();
ECKeyPair ourEphemeral = sessionRecord.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionRecord.setRootKey(senderChain.first);
sessionRecord.addReceiverChain(theirEphemeral, receiverChain.second);
sessionRecord.setPreviousCounter(sessionRecord.getSenderChainKey().getIndex()-1);
sessionRecord.setSenderChain(ourNewEphemeral, senderChain.second);
return receiverChain.second;
}
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
private MessageKeys getOrCreateMessageKeys(SessionRecordV2 sessionRecord,
ECPublicKey theirEphemeral,
ChainKey chainKey, int counter)
throws InvalidMessageException
{
if (chainKey.getIndex() > counter) {
if (sessionRecord.hasMessageKeys(theirEphemeral, counter)) {
return sessionRecord.removeMessageKeys(theirEphemeral, counter);
} else {
throw new InvalidMessageException("Received message with old counter!");
}
}
if (chainKey.getIndex() - counter > 500) {
throw new InvalidMessageException("Over 500 messages into the future!");
}
while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys();
sessionRecord.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey();
}
sessionRecord.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);
}
}

View File

@@ -21,26 +21,10 @@ import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
public class Curve {
public static final int NIST_TYPE = 0x02;
private static final int NIST_TYPE2 = 0x03;
public static final int DJB_TYPE = 0x05;
public static ECKeyPair generateKeyPairForType(int keyType) {
if (keyType == DJB_TYPE) {
return Curve25519.generateKeyPair();
} else if (keyType == NIST_TYPE || keyType == NIST_TYPE2) {
return CurveP256.generateKeyPair();
} else {
throw new AssertionError("Bad key type: " + keyType);
}
}
public static ECKeyPair generateKeyPairForSession(int messageVersion) {
if (messageVersion <= CiphertextMessage.LEGACY_VERSION) {
return generateKeyPairForType(NIST_TYPE);
} else {
return generateKeyPairForType(DJB_TYPE);
}
public static ECKeyPair generateKeyPair(boolean ephemeral) {
return Curve25519.generateKeyPair(ephemeral);
}
public static ECPublicKey decodePoint(byte[] bytes, int offset)
@@ -50,21 +34,13 @@ public class Curve {
if (type == DJB_TYPE) {
return Curve25519.decodePoint(bytes, offset);
} else if (type == NIST_TYPE || type == NIST_TYPE2) {
return CurveP256.decodePoint(bytes, offset);
} else {
throw new InvalidKeyException("Unknown key type: " + type);
}
}
public static ECPrivateKey decodePrivatePoint(int type, byte[] bytes) {
if (type == DJB_TYPE) {
return new DjbECPrivateKey(bytes);
} else if (type == NIST_TYPE || type == NIST_TYPE2) {
return CurveP256.decodePrivatePoint(bytes);
} else {
throw new AssertionError("Bad key type: " + type);
}
public static ECPrivateKey decodePrivatePoint(byte[] bytes) {
return new DjbECPrivateKey(bytes);
}
public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey)
@@ -76,8 +52,6 @@ public class Curve {
if (publicKey.getType() == DJB_TYPE) {
return Curve25519.calculateAgreement(publicKey, privateKey);
} else if (publicKey.getType() == NIST_TYPE || publicKey.getType() == NIST_TYPE2) {
return CurveP256.calculateAgreement(publicKey, privateKey);
} else {
throw new InvalidKeyException("Unknown type: " + publicKey.getType());
}

View File

@@ -37,10 +37,10 @@ public class Curve25519 {
private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic);
private static native byte[] generatePublicKey(byte[] privateKey);
private static native byte[] generatePrivateKey(byte[] random);
private static native byte[] generatePrivateKey(byte[] random, boolean ephemeral);
public static ECKeyPair generateKeyPair() {
byte[] privateKey = generatePrivateKey();
public static ECKeyPair generateKeyPair(boolean ephemeral) {
byte[] privateKey = generatePrivateKey(ephemeral);
byte[] publicKey = generatePublicKey(privateKey);
return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey));
@@ -65,11 +65,11 @@ public class Curve25519 {
return new DjbECPublicKey(keyBytes);
}
private static byte[] generatePrivateKey() {
private static byte[] generatePrivateKey(boolean ephemeral) {
byte[] privateKey = new byte[32];
random.nextBytes(privateKey);
return generatePrivateKey(privateKey);
return generatePrivateKey(privateKey, ephemeral);
}
}

View File

@@ -1,122 +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 android.util.Log;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class CurveP256 {
private static final BigInteger q = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16);
private static final BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16);
private static final BigInteger b = new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16);
private static final BigInteger n = new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16);
private static final ECFieldElement x = new ECFieldElement.Fp(q, new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16));
private static final ECFieldElement y = new ECFieldElement.Fp(q, new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16));
private static final ECCurve curve = new ECCurve.Fp(q, a, b);
private static final ECPoint g = new ECPoint.Fp(curve, x, y, true);
private static final ECDomainParameters domainParameters = new ECDomainParameters(curve, g, n);
public static final int P256_POINT_SIZE = 33;
static byte[] encodePoint(ECPoint point) {
synchronized (curve) {
return point.getEncoded();
}
}
static ECPublicKey decodePoint(byte[] encoded, int offset)
throws InvalidKeyException
{
byte[] pointBytes = new byte[P256_POINT_SIZE];
System.arraycopy(encoded, offset, pointBytes, 0, pointBytes.length);
synchronized (curve) {
ECPoint Q;
try {
Q = curve.decodePoint(pointBytes);
} catch (RuntimeException re) {
throw new InvalidKeyException(re);
}
return new NistECPublicKey(new ECPublicKeyParameters(Q, domainParameters));
}
}
static ECPrivateKey decodePrivatePoint(byte[] encoded) {
BigInteger d = new BigInteger(encoded);
return new NistECPrivateKey(new ECPrivateKeyParameters(d, domainParameters));
}
static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) {
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(((NistECPrivateKey)privateKey).getParameters());
synchronized (curve) {
return agreement.calculateAgreement(((NistECPublicKey)publicKey).getParameters()).toByteArray();
}
}
public static ECKeyPair generateKeyPair() {
try {
synchronized (curve) {
ECKeyGenerationParameters keyParamters = new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG"));
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(keyParamters);
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
keyPair = cloneKeyPairWithPointCompression(keyPair);
return new ECKeyPair(new NistECPublicKey((ECPublicKeyParameters)keyPair.getPublic()),
new NistECPrivateKey((ECPrivateKeyParameters)keyPair.getPrivate()));
}
} catch (NoSuchAlgorithmException nsae) {
Log.w("CurveP256", nsae);
throw new AssertionError(nsae);
}
}
// This is dumb, but the ECPublicKeys that the generator makes by default don't have point compression
// turned on, and there's no setter. Great.
private static AsymmetricCipherKeyPair cloneKeyPairWithPointCompression(AsymmetricCipherKeyPair keyPair) {
ECPublicKeyParameters publicKey = (ECPublicKeyParameters)keyPair.getPublic();
ECPoint q = publicKey.getQ();
return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(new ECPoint.Fp(q.getCurve(), q.getX(), q.getY(), true),
publicKey.getParameters()), keyPair.getPrivate());
}
}

View File

@@ -1,63 +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.spongycastle.crypto.params.ECPublicKeyParameters;
public class NistECPublicKey implements ECPublicKey {
private final ECPublicKeyParameters publicKey;
NistECPublicKey(ECPublicKeyParameters publicKey) {
this.publicKey = publicKey;
}
@Override
public byte[] serialize() {
return CurveP256.encodePoint(publicKey.getQ());
}
@Override
public int getType() {
return Curve.NIST_TYPE;
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof NistECPublicKey)) return false;
NistECPublicKey that = (NistECPublicKey)other;
return publicKey.getQ().equals(that.publicKey.getQ());
}
@Override
public int hashCode() {
return publicKey.getQ().hashCode();
}
@Override
public int compareTo(ECPublicKey another) {
return publicKey.getQ().getX().toBigInteger()
.compareTo(((NistECPublicKey) another).publicKey.getQ().getX().toBigInteger());
}
public ECPublicKeyParameters getParameters() {
return publicKey;
}
}

View File

@@ -2,15 +2,14 @@ package org.whispersystems.textsecure.crypto.protocol;
public interface CiphertextMessage {
public static final int LEGACY_VERSION = 1;
public static final int CURRENT_VERSION = 2;
public static final int UNSUPPORTED_VERSION = 1;
public static final int CURRENT_VERSION = 2;
public static final int LEGACY_WHISPER_TYPE = 1;
public static final int CURRENT_WHISPER_TYPE = 2;
public static final int PREKEY_WHISPER_TYPE = 3;
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 = WhisperMessageV1.ENCRYPTED_MESSAGE_OVERHEAD;
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
public byte[] serialize();
public int getType();

View File

@@ -7,6 +7,7 @@ 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;
@@ -14,19 +15,19 @@ 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 WhisperMessageV2 message;
private final byte[] serialized;
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.lowBitsToInt(serialized[0]);
this.version = Conversions.highBitsToInt(serialized[0]);
if (this.version > CiphertextMessage.CURRENT_VERSION) {
throw new InvalidVersionException("Unknown version: " + this.version);
@@ -49,16 +50,18 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
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 WhisperMessageV2(preKeyWhisperMessage.getMessage().toByteArray());
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, WhisperMessageV2 message)
IdentityKey identityKey, WhisperMessage message)
{
this.version = CiphertextMessage.CURRENT_VERSION;
this.registrationId = registrationId;
@@ -95,7 +98,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
return baseKey;
}
public WhisperMessageV2 getWhisperMessage() {
public WhisperMessage getWhisperMessage() {
return message;
}
@@ -106,7 +109,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
@Override
public int getType() {
return CiphertextMessage.PREKEY_WHISPER_TYPE;
return CiphertextMessage.PREKEY_TYPE;
}
}

View File

@@ -1,27 +1,24 @@
package org.whispersystems.textsecure.crypto.protocol;
import android.util.Log;
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.crypto.protocol.WhisperProtos.WhisperMessage;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class WhisperMessageV2 implements CiphertextMessage {
public class WhisperMessage implements CiphertextMessage {
private static final int MAC_LENGTH = 8;
@@ -31,18 +28,22 @@ public class WhisperMessageV2 implements CiphertextMessage {
private final byte[] ciphertext;
private final byte[] serialized;
public WhisperMessageV2(byte[] serialized) throws InvalidMessageException {
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));
}
WhisperMessage whisperMessage = WhisperMessage.parseFrom(message);
WhisperProtos.WhisperMessage whisperMessage = WhisperProtos.WhisperMessage.parseFrom(message);
if (!whisperMessage.hasCiphertext() ||
!whisperMessage.hasCounter() ||
@@ -65,11 +66,11 @@ public class WhisperMessageV2 implements CiphertextMessage {
}
}
public WhisperMessageV2(SecretKeySpec macKey, ECPublicKey senderEphemeral,
int counter, int previousCounter, byte[] ciphertext)
public WhisperMessage(SecretKeySpec macKey, ECPublicKey senderEphemeral,
int counter, int previousCounter, byte[] ciphertext)
{
byte[] version = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
byte[] message = WhisperMessage.newBuilder()
byte[] message = WhisperProtos.WhisperMessage.newBuilder()
.setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize()))
.setCounter(counter)
.setPreviousCounter(previousCounter)
@@ -103,7 +104,7 @@ public class WhisperMessageV2 implements CiphertextMessage {
byte[] ourMac = getMac(macKey, parts[0]);
byte[] theirMac = parts[1];
if (!Arrays.equals(ourMac, theirMac)) {
if (!MessageDigest.isEqual(ourMac, theirMac)) {
throw new InvalidMessageException("Bad Mac!");
}
}
@@ -129,7 +130,12 @@ public class WhisperMessageV2 implements CiphertextMessage {
@Override
public int getType() {
return CiphertextMessage.CURRENT_WHISPER_TYPE;
return CiphertextMessage.WHISPER_TYPE;
}
public static boolean isLegacy(byte[] message) {
return message != null && message.length >= 1 &&
Conversions.highBitsToInt(message[0]) <= CiphertextMessage.UNSUPPORTED_VERSION;
}
}

Some files were not shown because too many files have changed in this diff Show More