Compare commits

...

449 Commits

Author SHA1 Message Date
Moxie Marlinspike
0ca8a480c5 Bump version to 2.9.2
// FREEBIE
2015-03-27 15:29:54 -07:00
Jake McGinty
349faf18fa Fix missing timestamp in SendReq
// FREEBIE
2015-03-27 15:02:01 -07:00
Moxie Marlinspike
88ed8f3887 Bump version to 2.9.1
// FREEBIE
2015-03-27 11:19:20 -07:00
Jake McGinty
6a6f900b7d Fix NPE when 'from' missing in NotificationInd
Fixes #2806
// FREEBIE
2015-03-27 10:59:04 -07:00
Jake McGinty
17383ddfe0 fix NPE for incoming failed messages
Fixes #2807
// FREEBIE
2015-03-27 10:37:59 -07:00
agrajaghh
1b846c0842 remove unused resources
Closes #2594

//FREEBIE
2015-03-26 16:29:32 -07:00
Sam Whited
dbda758841 Add pref to change MMS user agent
Closes #2363

// FREEBIE
2015-03-26 15:48:53 -07:00
Moxie Marlinspike
c6b3bb9f13 Bump version to 2.9.0
// FREEBIE
2015-03-26 11:28:39 -07:00
Moxie Marlinspike
bdeeea2e46 Update language translations.
// FREEBIE
2015-03-26 11:13:55 -07:00
Jake McGinty
427c9a6b21 Use new MMS APIs in Lollipop onwards
Fixes #1937
Closes #2727
2015-03-26 11:05:41 -07:00
Moxie Marlinspike
dfda2f733c Upgrade to libtextsecure 1.2.4
Fixes #2790

// FREEBIE
2015-03-25 14:13:53 -07:00
agrajaghh
6f600a80b8 fix merge conflict between #2743 and #2746
Closes #2789

//FREEBIE
2015-03-25 13:28:57 -07:00
agrajaghh
62181a3d7f migrate AlertDialog to material-dialogs
Closes #2743
2015-03-25 11:50:52 -07:00
Moxie Marlinspike
6c386d3fc9 Add http to invite url.
// FREEBIE
2015-03-25 11:18:38 -07:00
Moxie Marlinspike
b9e6cff20c Fix the build
Fixes #2787
2015-03-25 10:55:47 -07:00
Moxie Marlinspike
d65a7f1a25 Update invite strings.
// FREEBIE

Closes #2735
2015-03-25 10:49:52 -07:00
Rhodey Orbits
76d0f45b9b append invite link to message draft instead of overwrite it.
Fixes #2583
// FREEBIE
2015-03-25 10:36:36 -07:00
agrajaghh
3a19ac432b Display the phone number in the confirm dialog during registration
Closes #2746
2015-03-25 09:24:28 -07:00
NorthwestBased
6ec9ee89a7 Disallow zero-length passphrases.
Fixes #2751
Closes #2762
2015-03-25 09:19:53 -07:00
Moxie Marlinspike
472179b550 Ignores MMS WAP_PUSH_DELIVER_ACTION if we're not the default app.
Apparently some ROMs have a bug where they broadcast
WAP_PUSH_DELIVER_ACTION even when you're not the system's default
SMS/MMS app.

Fixes #2777
2015-03-25 08:57:08 -07:00
Rhodey Orbits
f38bd7b187 fix initialization of emoji cache
Fixes #2700
Closes #2719

// FREEBIE
2015-03-23 15:02:31 -07:00
Jake McGinty
b73143d7a7 fix send button assets
Closes #2757

// FREEBIE
2015-03-23 13:54:37 -07:00
Jake McGinty
9a2c372c23 fix crash on android contact weirdness
Fixes #2722
Closes #2758

// FREEBIE
2015-03-23 13:53:22 -07:00
Moxie Marlinspike
b21369b7bc Bump version to 2.8.0
// FREEBIE
2015-03-19 12:55:41 -07:00
Moxie Marlinspike
5939a1f995 Upgrade libtextsecure to 1.2.3
// FREEBIE
2015-03-19 12:41:07 -07:00
Moxie Marlinspike
e4b73e90e1 Update language translations
// FREEBIE
2015-03-19 12:02:58 -07:00
Jake McGinty
073d019103 Fix SMIL regression
Closes #2654

// FREEBIE
2015-03-19 11:56:45 -07:00
Jake McGinty
761ccf4b3f Fix for inconsistent thumbnail aspect ratio
Problems arose from any app that targets below API 19 and uses
views that rely on RelativeLayouts giving correct measurement specs to
their onMeasure().

Resolves #2676
Closes #2712
// FREEBIE
2015-03-19 11:21:40 -07:00
Moxie Marlinspike
7a023b9fdc Acquire wakelocks for incoming messages.
Closes #2717

// FREEBIE
2015-03-19 11:19:53 -07:00
Jake McGinty
9f804047f5 lazily initialize emoji drawer
Saves a good amount of inflation time in onCreate for new conversations.

Closes #2585
Closes #2636

// FREEBIE
2015-03-19 11:16:52 -07:00
Rhodey Orbits
a625727853 String should not be compared with an Optional in DeliveryReceiptJobTest.testDelivery().
Closes #2713

// FREEBIE
2015-03-18 14:28:29 -07:00
Moxie Marlinspike
e0737451db Bump version to 2.7.1
// FREEBIE
2015-03-17 10:21:12 -07:00
agrajaghh
0f572d1e5d adapt SMS strings in preferences
Fixes #2672
Closes #2682

//FREEBIE
2015-03-16 11:02:22 -07:00
Moxie Marlinspike
142f84703b Make clickable conversation items batch selectable
Fixes #2679

// FREEBIE
2015-03-16 10:32:39 -07:00
Jake McGinty
b47bd97e9e fix message details 12-hour time formatting
Resolves #2695
// FREEBIE
2015-03-15 20:28:23 -07:00
Jake McGinty
4099303d1f make dark theme go less wild
Resolves #2675
Closes #2693

// FREEBIE
2015-03-15 09:33:10 -07:00
Jake McGinty
71eeb7ca83 Fix ClassCastException for notifications
Resolves #2691
Closes #2692

// FREEBIE
2015-03-15 09:31:21 -07:00
Jake McGinty
186a5e4dfc update libpastelog to 1.0.6
adds memory information to debug output
// FREEBIE
2015-03-13 17:52:31 -07:00
Moxie Marlinspike
0d84ec51a8 Update README with beta instructions.
// FREEBIE
2015-03-13 12:16:13 -07:00
Moxie Marlinspike
6c9ed77e5f Bump version to 2.7.0-B1
// FREEBIE
2015-03-13 12:09:56 -07:00
Moxie Marlinspike
fc3aaed8a8 Remove unused layouts and strings.
// FREEBIE
2015-03-13 11:58:49 -07:00
Moxie Marlinspike
c72cc1b49c Updated language translations.
// FREEBIE
2015-03-13 11:43:49 -07:00
Jake McGinty
13227b6aa5 disable fancy new thumbnail preview
It was causing OOMEs on old devices.
// FREEBIE
2015-03-13 11:30:15 -07:00
Moxie Marlinspike
cc65fab226 Update message details activity display when resending SMS/MMS
// FREEBIE
2015-03-13 11:29:44 -07:00
agrajaghh
dee2e0906e remove "Complete key exchanges" from advanced options
Closes #2664

//FREEBIE
2015-03-13 12:09:37 +01:00
Moxie Marlinspike
e142cb6d26 Prompt for registration on install until successful.
Closes #2657

// FREEBIE
2015-03-12 18:36:34 -07:00
Jake McGinty
4185006147 add borderless thumbnails, "bubble" refactor
Closes #2430

// FREEBIE
2015-03-12 18:23:08 -07:00
Moxie Marlinspike
a4e18c515c Remove encrypted SMS transport, simplify transport options.
Closes #2647

// FREEBIE
2015-03-12 17:53:38 -07:00
Rhodey Orbits
2011391e65 Fix notifications for media messages without text.
They now read 'media message' in italics.

Closes #2649
2015-03-12 17:30:23 -07:00
Jake McGinty
96896bf8f1 disconnect radio regardless of MMS exception
Fixes #2498
Closes #2544
// FREEBIE
2015-03-12 12:29:32 -07:00
agrajaghh
ac312ff5d5 change FAB icon to ic_create and add more resolutions to ic_add
Closes #2634

//FREEBIE
2015-03-12 11:31:03 -07:00
agrajaghh
b3552ee347 add dynamicTheme and dynamicLanguage to MessageDetailsActivity
Closes #2640

//FREEBIE
2015-03-12 11:22:12 -07:00
Jake McGinty
b998c5f86c Add missing MSISDN headers to MMS send request
Fixes #1935
Fixes #2451
Closes #2605

// FREEBIE
2015-03-12 11:18:20 -07:00
Jake McGinty
0b8430ff43 update APN list, fix MMSC issues
Fixes #2413
Closes #2606

// FREEBIE
2015-03-12 11:15:14 -07:00
Moxie Marlinspike
3e80032c18 Bump version to 2.6.4
// FREEBIE
2015-03-11 09:24:28 -07:00
Moxie Marlinspike
41fadc3437 Use libtextsecure 1.2.2
// FREEBIE
2015-03-10 18:59:12 -07:00
Moxie Marlinspike
c1fe585ffb Bump version to 2.6.3
// FREEBIE
2015-03-09 13:32:11 -07:00
Moxie Marlinspike
078df0f029 Update key conflict message in place.
// FREEBIE
2015-03-09 13:15:24 -07:00
Moxie Marlinspike
9f9293cd61 Pending indicator was not cleared on recycled views.
// FREEBIE
2015-03-09 13:14:39 -07:00
Moxie Marlinspike
4e87513867 Bump version to 2.6.2
// FREEBIE
2015-03-08 11:45:56 -07:00
Moxie Marlinspike
c05aca19ae Minimal-diff change to fix NPE.
Fixes #2623

// FREEBIE
2015-03-08 11:37:18 -07:00
Moxie Marlinspike
db2c7fabe1 Bump version to 2.6.1
// FREEBIE
2015-03-07 09:28:28 -08:00
Moxie Marlinspike
e7eb0ec2b3 Fix for missing messages bug
Fixes #2609

// FREEBIE
2015-03-07 09:02:59 -08:00
Moxie Marlinspike
da75268545 Temporary fix until jake fixes this shit
// FREEBIE
2015-03-07 09:02:38 -08:00
Moxie Marlinspike
49ab1e7bd5 Bump version to 2.6.0
// FREEBIE
2015-03-06 11:07:53 -08:00
Moxie Marlinspike
b5a2d7882a Updated language translations.
// FREEBIE
2015-03-06 10:10:34 -08:00
Moxie Marlinspike
534df06794 Support granular "custom" MMS preferences.
1) Make each MMS preference an individual choice between custom
   and default.

2) Display default values.

Closes #2487

// FREEBIE
2015-03-05 18:42:34 -08:00
agrajaghh
e31ddf0599 remove unused icons
Closes #2575

//FREEBIE
2015-03-05 18:36:10 -08:00
Rhodey Orbits
ce22e54359 Update message timesamps on resume.
Fixes #2519.
Closes #2593

// FREEBIE
2015-03-05 18:20:39 -08:00
Rhodey Orbits
7ce7ec56eb 1) created a new string constant for 'Passphrase'.
2) modified 'App protection' preference group summary to be more verbose.
3) dropped the colons from 'Appearance' preference group summary.
4) dropped the colons from and 'SMS and MMS' preference group summary.
2015-03-05 18:20:39 -08:00
agrajaghh
2b39728ea0 fix icons in import/export dialog
Closes #2574

//FREEBIE
2015-03-05 17:35:25 -08:00
Jake McGinty
2af5c7f02e fix "verify" button spacing in details
Fixes #2566
// FREEBIE
2015-03-05 17:07:07 -08:00
Jake McGinty
d6e0199e05 fix MessageDetailsActivity home button
// FREEBIE
2015-03-05 17:07:07 -08:00
Moxie Marlinspike
301ed0b255 Move passphrase disabled preference set to after secret generation.
Fixes #2588
2015-03-05 13:09:18 -08:00
Moxie Marlinspike
ac5306be5b Fix locale names
// FREEBIE

Closes #2564
2015-03-05 11:45:09 -08:00
agrajaghh
9de62d0286 replace icons in the add attachment dialog
Closes #2535
2015-03-03 17:14:31 -08:00
agrajaghh
067b14d551 make some strings translateable
closes #2567

//FREEBIE
2015-03-03 17:09:07 -08:00
agrajaghh
03110c9870 remove ConfirmIdentityActivity
Closes #2571

//FREEBIE
2015-03-03 17:06:24 -08:00
agrajaghh
a2b5bf54c8 remove PassphraseTimeoutPreference since its no longer used
closes #2565

//FREEBIE
2015-03-03 17:03:17 -08:00
Moxie Marlinspike
5602a3dfc0 Update to latest version of libtextsecure for simplified interface.
1) Switch to new TextSecureAddress addressing, rather than mixing
   long-based recipient IDs into libtextsecure.

2) Get rid of RecipientFormattingException throws in calls to
   RecipientFactory.

Closes #2570
2015-03-03 12:25:09 -08:00
Moxie Marlinspike
ed5b3f8679 Break out libtextsecure
// FREEBIE

Closes #2542
2015-03-02 08:25:19 -08:00
Moxie Marlinspike
7bf7acb1ff Dependency verification.
// FREEBIE

Closes #2546
2015-02-28 09:40:52 -08:00
Moxie Marlinspike
ffa5dc549b Add libtextsecure README
// FREEBIE
2015-02-27 16:58:19 -08:00
Moxie Marlinspike
a59feb7ad4 Rename PushAddress to TextSecureAddress
// FREEBIE
2015-02-27 16:57:51 -08:00
Jake McGinty
753a1c7219 add FAB content description
// FREEBIE
2015-02-27 16:31:01 -08:00
Moxie Marlinspike
004f050741 libtextsecure javadoc and minor API refactoring
// FREEBIE
2015-02-27 15:35:18 -08:00
Jake McGinty
fcde642563 Fix invalid transportation options
1) Accounts in the directory that don't have supports_sms can't
   get encrypted SMS.
2) Push groups can't receive insecure SMS

// FREEBIE

Closes #2521
2015-02-27 13:00:18 -08:00
Jake McGinty
ac0a1c0bc1 Conflicts UI touch-ups:
1) Fix i18n and layout naming.
2) Error header for message details.
3) Add back button to message details.

// FREEBIE

Closes #2593
2015-02-27 12:26:35 -08:00
Moxie Marlinspike
3f93731300 Adapt Conflicts UI to work with single-recipient failures. 2015-02-27 12:26:09 -08:00
Moxie Marlinspike
00d7b5c284 Better UX handling on identity key mismatches.
1) Migrate from GSON to Jackson everywhere.

2) Add support for storing identity key conflicts on message rows.

3) Add limited support for surfacing identity key conflicts in UI.
2015-02-27 12:26:09 -08:00
Jake McGinty
4397b55ceb fix padding problem in compose text
// FREEBIE
2015-02-25 14:53:32 -08:00
Martin Stone
b87abcc2c1 Update lockscreen image to new (blue) logo.
Fixes #2426
Closes #2502
2015-02-24 16:45:12 -08:00
Sebastian Kürten
79dbf2746f Added new media icons for audio/video
Added icons new icons, deleted an old one.
Using new icons in AudioSlide/VideoSlide.

Closes #2304
2015-02-23 13:13:39 -08:00
agrajaghh
a4bd9fae9a make "repeat alerts" setting translatable
Fixes #2511
Closes #2516

 //FREEBIE
2015-02-23 09:54:28 -08:00
Moxie Marlinspike
d4493e700e Add GroupId for received messages.
// FREEBIE
2015-02-23 09:53:32 -08:00
Moxie Marlinspike
83d65228e9 Fix for race conditioned caused by OkHttpClient NPE.
We catch OkHttpClient exceptions to deal with bugs in their code,
but in some cases that was leaving our state information in a
bad situation.

// FREEBIE
2015-02-23 09:53:19 -08:00
Moxie Marlinspike
8a2caeef3d Fix for OKH IllegalStateException.
// FREEBIE

Fixes #2501
2015-02-20 12:45:42 -08:00
Jake McGinty
6280ef4ab8 Revert "set appcompat menu class to obfuscate-only in PG"
This reverts commit 899afa2092.

Was able to reproduce a crash with this in second test that was actually
resolved in the previous proguard. Reverting.
2015-02-19 16:27:39 -08:00
Jake McGinty
899afa2092 set appcompat menu class to obfuscate-only in PG
keeping it on the safe side - tested on problematic samsung devices, and
it looks like it works fine.
// FREEBIE
2015-02-19 15:36:57 -08:00
Moxie Marlinspike
7c6b965969 Shrink some SC classes for Gingerbread dexopt limit.
// FREEBIE
2015-02-19 12:32:54 -08:00
Jake McGinty
5fac189736 "All images" view for conversations
// FREEBIE
2015-02-18 15:47:47 -08:00
Moxie Marlinspike
d3271f548c Support for retrieving stored messages via websocket.
1) When registering with server, indicate that the server should
   store messages and send notifications.

2) Process notification GCM messages, and connect to the server
   to retrieve actual message content.
2015-02-18 11:09:01 -08:00
Jake McGinty
023195dd4b make send button focusable
Fixes #2494
// FREEBIE
2015-02-17 17:55:18 -08:00
Moxie Marlinspike
299dd86c77 Support matching to account for telcos inserting SMS prefixes.
// FREEBIE

Fixes #1944
2015-02-16 20:25:19 -08:00
Jake McGinty
f818cfa32b Unjank character count indicators
Fixes #1841
// FREEBIE
2015-02-16 13:25:44 -08:00
Jake McGinty
fc21d2038f Add first Espresso interaction test
// FREEBIE
2015-02-16 13:08:28 -08:00
Jake McGinty
8277e4e102 Fix NPE when InetAddress.getByName() is null
Resolves #2189
// FREEBIE
2015-02-16 13:07:59 -08:00
Jake McGinty
242dbef54e Prevent NPE when MMS's FROM PduHeader is missing
Fixes #1448
// FREEBIE
2015-02-16 13:07:30 -08:00
Jake McGinty
9ffd84ce05 Increase readability of recv date in dark theme
Fixes #1483
// FREEBIE
2015-02-15 22:09:02 -08:00
Moxie Marlinspike
cb37ce0f5f Fix README typo
// FREEBIE

Closes #2396
2015-02-15 14:41:36 -08:00
Andy Martin
7ab739b22b clear the current text box when deleting a draft-only thread.
otherwise the draft will be immediately re-saved (preventing
thread deletion).

// FREEBIE

Closes #2474
Fixes #2473
2015-02-15 09:41:55 -08:00
Moxie Marlinspike
224b06f05a Theme the HMS picker.
// FREEBIE

Closes #2353
2015-02-13 15:06:50 -08:00
McLoo
d36d33f837 Stop crash on pasting long numbers as country code
//FREEBIE

Closes #2311
2015-02-13 14:38:35 -08:00
Moxie Marlinspike
724dced820 Define a max message length to display.
// FREEBIE

Closes #2294
2015-02-13 14:32:11 -08:00
Sam Lanning
b8ca0e57ad Only initialise EmojiGrid when not in edit mode
Suppress initialisation of the emoji_grid_layout.xml when the
EmojiDrawer is being used in a layout where the editor is shown,
 to prevent an NPE when an editor is being used.

// FREEBIE

Closes #2288
2015-02-13 14:23:05 -08:00
AsamK
fe5fc411a7 Save drafts for recipients without an existing thread
- Save drafts without thread in draft database
- Clear drafts when deleting conversation(s), otherwise
  the drafts stay in the database and will reappear when a thread
  with the same threadId is created

Closes #1510
2015-02-13 14:16:19 -08:00
agrajaghh
20d43bbfcb add icons to the preference categories
Closes #2361
2015-02-13 11:18:04 -08:00
Jake McGinty
442f56cdc3 prevent NPE in GroupCreateActivity
Occurs when a returned Recipient is null when returning from contact
selection activity.

Fixes #2466
// FREEBIE
2015-02-12 13:36:47 -08:00
Moxie Marlinspike
35dabaf1e5 Bump version to 2.5.3
// FREEBIE
2015-02-12 13:23:53 -08:00
Jake McGinty
1aec794a34 widen LGE menu workaround constraints
// FREEBIE
2015-02-12 13:16:03 -08:00
Jake McGinty
5bd8d6c69d Check the part stream is non-null before decoding
// FREEBIE

Closes #2459
2015-02-12 10:32:53 -08:00
Moxie Marlinspike
aa26785c00 Reinclude SC provider for consistency.
// FREEBIE

Fixes #2461
2015-02-12 10:30:45 -08:00
Moxie Marlinspike
f450b37cfd Bump version to 2.5.2
// FREEBIE
2015-02-11 23:24:10 -08:00
Jake McGinty
b88e470594 prevent recipient listener Activity context leak
Resolves #2372
Resolves #2425
// FREEBIE
2015-02-11 12:50:20 -08:00
Jake McGinty
f38677794a stop giving long-living static objects Activity contexts
// FREEBIE
2015-02-11 12:50:19 -08:00
Jake McGinty
ac4db41435 lower memory consumption from previews
// FREEBIE
2015-02-11 12:50:19 -08:00
Jake McGinty
3a9d521ffe Workaround LG-related menu issues, pt. 2
fixes #2444
// FREEBIE
2015-02-11 12:44:40 -08:00
Jake McGinty
b1bf33b13b Fix SaveAttachmentTask NPE
resolves #2454
// FREEBIE
2015-02-11 11:42:36 -08:00
Moxie Marlinspike
352418d2d7 Don't notify on MMS delivery failure if message has been deleted.
// FREEBIE

Fixes #2453
2015-02-11 10:29:05 -08:00
Moxie Marlinspike
fb523985ed Bump version to 2.5.1
// FREEBIE
2015-02-09 13:45:41 -08:00
Moxie Marlinspike
7ffe6f053c Merge pull request #2435 from mcginty/dark-actionmode-popup
Fix white-on-white ActionMode popup in dark theme
2015-02-09 12:07:54 -08:00
Jake McGinty
ea77191b70 Fix white-on-white ActionMode popup
// FREEBIE
2015-02-09 12:00:52 -08:00
Moxie Marlinspike
e0b882d4d2 Merge pull request #2434 from mcginty/plurals
move plurals to strings.xml
2015-02-09 11:24:58 -08:00
Jake McGinty
10a74d6d08 move plurals to strings.xml
Resolves #2421
// FREEBIE
2015-02-09 11:19:30 -08:00
Jake McGinty
f092e85b62 Workaround for LGE-related NPEs
See: https://code.google.com/p/android/issues/detail?id=78154

Fixes #2424
Closes #2428

// FREEBIE
2015-02-09 09:15:44 -08:00
Moxie Marlinspike
8aa0f15740 Bump version to 2.5.0
// FREEBIE
2015-02-06 16:42:04 -08:00
Moxie Marlinspike
18d4d4de24 Update language translations
// FREEBIE
2015-02-06 16:21:16 -08:00
Moxie Marlinspike
b3c42dee7e Merge pull request #2416 from mcginty/better-relative-dates
more succinct relative dates
2015-02-06 16:20:23 -08:00
Moxie Marlinspike
5836f35291 Merge pull request #2400 from mcginty/mms-bad-base64-fix
Fix NPE when encrypted MMS has bad base64 encoding
2015-02-06 16:19:20 -08:00
Jake McGinty
dbd8a4083c Fix NPE when encrypted MMS has bad base64 encoding
// FREEBIE
2015-02-06 13:01:36 -08:00
Jake McGinty
0fd52ad1fe more succinct relative dates
// FREEBIE
2015-02-06 13:00:21 -08:00
Moxie Marlinspike
8031c788d9 Merge pull request #2415 from mcginty/empty-group-updates
fix empty group update messages
2015-02-05 19:49:09 -08:00
Moxie Marlinspike
10a29db93d Merge pull request #2414 from mcginty/unread-readability
increase unread visibility in conv list
2015-02-05 19:46:31 -08:00
Jake McGinty
8f3f2e6921 fix empty group update messages
// FREEBIE
2015-02-05 19:38:08 -08:00
Jake McGinty
65c262acef increase unread visibility in conv list
// FREEBIE
2015-02-05 18:41:54 -08:00
AsamK
d586893402 Also crop contact photos in ShareList
// FREEBIE
2015-02-05 14:14:48 -08:00
Jake McGinty
ba62e018db Refine icon
1) fix glow
2) improve shadows
3) fix density-inconsistent assets
2015-01-28 12:52:51 -10:00
Moxie Marlinspike
57c17e705f Fix provisioning dialog strings.
// FREEBIE

Closes #2368
2015-01-24 10:16:33 -08:00
Jake McGinty
2e253fb6a8 provisioning dialog rework
// FREEBIE
2015-01-24 10:15:47 -08:00
Moxie Marlinspike
0c32001fe4 Support for multi-device provisioning flow.
// FREEBIE
2015-01-24 10:15:47 -08:00
Jake McGinty
48f6c2c526 Fix 'indicator tab' coloring
Was previously white-on-near-white
// FREEBIE
2015-01-23 10:59:22 -10:00
Jake McGinty
39fd1e8f46 fix incoming lock asset
// FREEBIE
2015-01-21 20:06:05 -10:00
Jake McGinty
810abe0275 material actionbar and fab
// FREEBIE
2015-01-21 19:01:23 -10:00
Jake McGinty
62816ee51a rounded ImageView instead of Bitmap, crop-select
// FREEBIE
2015-01-21 17:39:54 -10:00
Calvin Hu
a0599c1639 add theme to import/export activity
closes #2359
2015-01-16 10:41:01 -10:00
Jake McGinty
8a4a1b385d libpastelog 1.0.4
// FREEBIE
2015-01-15 13:06:42 -10:00
Moxie Marlinspike
5def8ba78d Bump version to 2.4.2
// FREEBIE
2015-01-14 13:51:53 -08:00
Jake McGinty
42da687602 Revert "add theme to import/export activity"
This reverts commit e64c067636.
2015-01-14 10:23:10 -10:00
Calvin Hu
e64c067636 add theme to import/export activity 2015-01-14 10:03:02 -10:00
Calvin Hu
c89fbabbf3 refresh contacts database on new db query 2015-01-13 15:16:30 -10:00
Moxie Marlinspike
778b8b490c Merge pull request #2346 from mcginty/thumbnail-early-recycle
fix early recycling of thumbnail
2015-01-13 16:21:15 -08:00
Jake McGinty
bade52d748 fix early recycling of thumbnail
// FREEBIE
2015-01-13 14:17:03 -10:00
Moxie Marlinspike
abc322b075 Bump version to 2.4.1
// FREEBIE
2015-01-13 16:01:58 -08:00
Moxie Marlinspike
9a31c5961a Merge pull request #2341 from mcginty/mms-recv-null-text
don't fail on null received text parts
2015-01-13 12:35:22 -08:00
Jake McGinty
f536e45378 don't fail on null received text parts
Fixes #2340
// FREEBIE
2015-01-13 10:15:18 -10:00
Moxie Marlinspike
18961e6369 Merge pull request #2339 from mcginty/content-security-exception
catch content security exception
2015-01-13 12:02:24 -08:00
Jake McGinty
1505ec8c35 catch ContentProvider SecurityExceptions to avoid crashes
// FREEBIE
2015-01-13 10:00:19 -10:00
Jake McGinty
d41efdbd1c prepareMessageMedia before we encrypt
and fail more nicely when pdu composition fails

// FREEBIE

Closes #2338
2015-01-13 11:59:03 -08:00
Jake McGinty
156cb4034e don't copy data when no dataUri
Fixes #2336
// FREEBIE
2015-01-13 11:57:46 -08:00
Moxie Marlinspike
d2e188ee62 Bump version to 2.4.0
// FREEBIE
2015-01-13 11:54:36 -08:00
Moxie Marlinspike
1de16de55b Update language translations.
// FREEBIE
2015-01-12 13:40:00 -08:00
Moxie Marlinspike
ac3b3673b9 Merge pull request #2330 from mcginty/dexmaker-workaround
workaround dexmaker bug in test cases
2015-01-12 13:38:57 -08:00
Jake McGinty
91bf5532b2 workaround dexmaker bug in test cases
// FREEBIE
2015-01-12 11:34:41 -10:00
Jake McGinty
b25b95f933 media constraints model
// FREEBIE
2015-01-12 13:20:25 -08:00
Moxie Marlinspike
a0ed0842a0 Add expiration check on build freshness.
// FREEBIE
2015-01-12 09:12:10 -08:00
Jake McGinty
12845da91a use PartDatabase to look up thumbnails async
// FREEBIE

Closes #2286
2015-01-11 18:10:53 -08:00
Jake McGinty
3e6e28e688 remove unused activities and fragments
// FREEBIE
2015-01-08 19:16:43 -08:00
Jake McGinty
9fed60628d fix temp file permission denied
// FREEBIE
2015-01-05 17:01:32 -08:00
Jake McGinty
e67dafe9c8 set conversation actionbar statelessly
// FREEBIE
2015-01-04 13:14:09 -08:00
Jake McGinty
5e5531805b disable android's landscape keyboard fullscreen
in composition, it stops the custom send icon from appearing, and
doesn't pay attention to theming.

// FREEBIE
2015-01-04 02:30:16 -08:00
Jake McGinty
b6822b310e don't populate data from uri when there is none
Fixes #2282
Closes #2283

// FREEBIE
2014-12-31 16:00:28 -08:00
Jake McGinty
3e8d2a557e Merge pull request #2281 from jonboulle/building
Remove native components from build instructions
2014-12-31 03:15:07 -08:00
Jonathan Boulle
23caf2b14d Remove native components from build instructions
Native components were split out into the libaxolotl-android repository
in 1833e57, so these instructions are no longer relevant.
2014-12-30 21:47:59 -08:00
Moxie Marlinspike
562520a4bb Remove empty strings.
// FREEBIE
2014-12-29 18:20:50 -08:00
Moxie Marlinspike
75d882e945 Updated language translations
// FREEBIE
2014-12-29 17:02:16 -08:00
Jake McGinty
b39e41c803 media preview orientation
// FREEBIE
2014-12-29 16:50:19 -08:00
Jake McGinty
348352cc71 Bitmap OOM and rotation fixes
// FREEBIE
2014-12-29 16:42:22 -08:00
Moxie Marlinspike
bec5e45605 Fix for old-skool SMS key exchange UI behavior.
Also fixes SMS "end session" messages failing to end session.

// FREEBIE
2014-12-29 15:09:13 -08:00
Moxie Marlinspike
4d8004ab21 Merge pull request #2276 from mcginty/group-meta-colors
group visual fixes
2014-12-29 14:37:45 -08:00
Jake McGinty
2e141b2a90 group visual fixes
// FREEBIE
2014-12-29 14:34:09 -08:00
Moxie Marlinspike
ed556fbd3a Support for multi-select in the conversation list.
// FREEBIE

Closes #1601
Closes #2214

Fixes #2188
Fixes #786
2014-12-29 13:54:59 -08:00
Jake McGinty
ebf6a2d833 remove pointless Bitmap allocation for ImageSlides
Fixes #2248
// FREEBIE
2014-12-29 13:49:37 -08:00
Moxie Marlinspike
25028b7178 Update to jobmanager 0.10.0
// FREEBIE
2014-12-29 13:32:37 -08:00
Jake McGinty
389128d1a3 fix edit-group intent extras
// FREEBIE
2014-12-29 12:58:54 -08:00
Moxie Marlinspike
6e85677c52 Support 'WiFi calling' devices.
// FREEBIE

Fixes #2254
Fixes #2240
Fixes #2160

Closes #2263
2014-12-29 12:51:39 -08:00
Moxie Marlinspike
1443d846c4 Use "singleTask" to ensure a single ConversationActivity instance.
1) Mark the Activity as singleTask.

2) Process onNewIntent for updates.

3) Propagate onNewIntent to the ConversationFragment.

Fixes #2232
Fixes #1532
Fixes #146
Fixes #851
Closes #2266
2014-12-29 12:49:15 -08:00
guiweber
d7419caa4b Moved several hard-coded UI strings to strings.xml
Closes #2203
2014-12-29 12:29:48 -08:00
Jake McGinty
e277f9f6d1 fix some missing recipient parcel bugs
// FREEBIE
2014-12-29 11:43:10 -08:00
McLoo
3f0932ff7b replace unlocalized 'at' in message details date
Making this a string resource has the potential for crashes.
Leaving it out completely is a little confusing.
The dash is the perfect solution - until a better date formatter is used.
Closes #2215
2014-12-26 11:05:34 -08:00
Jake McGinty
0a61534e03 fix recipient handling for media preview
// FREEBIE
2014-12-24 15:11:13 -08:00
Jake McGinty
121b1493cc thumbnail generation and disk caching
// FREEBIE
2014-12-24 14:46:50 -08:00
Moxie Marlinspike
a57c7c3e09 Merge pull request #2264 from mcginty/write-part-data-fix
use PartAuthority when fetching part streams
2014-12-24 14:07:42 -08:00
Jake McGinty
b89b986a9e use PartAuthority when fetching part streams
resolves the self-send case

Fixes #2259
// FREEBIE
2014-12-24 13:27:53 -08:00
Jake McGinty
1bb882ac96 fix bithub payment link and image 2014-12-24 13:22:31 -08:00
Jake McGinty
9e7928873b properly update transport options location
// FREEBIE
2014-12-24 13:17:41 -08:00
Moxie Marlinspike
21591dc6b1 Fix build.
// FREEBIE
2014-12-23 15:42:32 -08:00
McLoo
3ec95f7a8e More l10n - for decrypting errors
//FREEBIE

Closes #2231
2014-12-23 12:53:38 -08:00
Oliver Gasser
33d466a5cc Include group titles in search
When searching for messages only simple threads matching the
contact names are returned as search results. With this commit
also group converstations where the group title matches the search
term are displayed in the result. This makes search results more
consistent with the conversation list as now all conversation
titles (i.e. contact names and group titles) are searched through.

Fixes #1954
Closes #2216
2014-12-23 12:40:30 -08:00
Jake McGinty
e8b947dfde Pass recipient ids, not recipient objects
Fixes #2233
// FREEBIE
2014-12-22 14:53:28 -08:00
Moxie Marlinspike
60a343120d Merge pull request #2230 from mcginty/manual-mms-pref-validation
Catch issues with bad MMSC URIs, add validation
2014-12-16 18:00:52 -08:00
McLoo
8af74e93a2 Replace button text to cancel TS registration
in RegistrationActivity from "skip" to "cancel", when coming from the reminder

Closes #2219
2014-12-16 16:09:26 -08:00
Jake McGinty
e650349bb7 Catch issues with bad MMSC URIs, add validation
Fixes #2202
// FREEBIE
2014-12-16 16:01:22 -08:00
agrajaghh
44a4570870 remove double rotation of group avatars
//FREEBIE
2014-12-16 14:33:46 -08:00
Moxie Marlinspike
f1572774f2 Merge pull request #2227 from mcginty/circle-crop-oom
memory-friendly resize for circle cropping
2014-12-15 20:42:53 -08:00
Jake McGinty
ec18da8aa9 memory-friendly resize for circle cropping
Fixes #2224
// FREEBIE
2014-12-15 19:57:51 -08:00
Moxie Marlinspike
199c28dad6 Merge pull request #2218 from McLoo/fix_av_draft_2
Enabled audio and video drafts
2014-12-14 11:19:21 -08:00
McLoo
4a8a939f64 Enabled audio and video drafts
All slide types do have an "image", changing the check order enables audio and video drafts.
//FREEBIE
2014-12-14 20:16:35 +01:00
agrajaghh
cef203f261 rotate images based on exif information
Closes #1847
2014-12-13 16:45:39 -08:00
Moxie Marlinspike
d7de5415b5 Skip drafts and outbox messages during imports.
// FREEBIE

Closes #1468
Fixes #892
2014-12-12 19:35:52 -08:00
McLoo
188d11bc23 Added l10n for ZXing intent message box
Closes #2147
2014-12-12 19:05:31 -08:00
Tal Hacohen
990902845a Change creation of drafts to onStop method.
Drafts were created only on destroy of the conversation activity.
Moved it to be on stop for the activity.

Closes #1985
2014-12-12 18:51:52 -08:00
Jake McGinty
07bb07c342 streaming media
// FREEBIE
2014-12-12 18:49:32 -08:00
Aaron M Brown
a09e0afbd6 Explain size limit for large attachments in toasts.
// FREEBIE

Fixes #2100
Closes #2172
2014-12-12 18:45:06 -08:00
Aaron M Brown
bbea017d5a Make message details reflect 24-hour system preference.
Fixes #2150
Closes #2163
2014-12-12 18:37:40 -08:00
Moxie Marlinspike
426c56c77d Support install link
Closes #2001
Fixes #1722
2014-12-12 18:31:20 -08:00
Moxie Marlinspike
642334da1f Merge pull request #2212 from agrajaghh/betterpicker_translateable
make summary of passphrase timeout translatable
2014-12-12 17:04:29 -08:00
McLoo
b44acd82e6 Added l10n for message details
//FREEBIE

Closes #2173
2014-12-12 16:43:45 -08:00
agrajaghh
62d0de2ee6 remove double include of betterpicker in build.gradle
//FREEBIE
2014-12-13 01:38:03 +01:00
agrajaghh
3c7e319029 make summary of passphrase timeout translatable
//FREEBIE
2014-12-13 01:37:06 +01:00
Lukas Barth
715b1ea10d Reset query string when search is collapsed
Closes #2041
2014-12-12 16:30:17 -08:00
ultrasandwich
6980a32628 Fix user-facing helping term 'insecure' to 'unsecured'.
Insecure implies an uncertainty of intention or confidence.
Unsecured is more specifically addressing the protocol which
wraps the message delivered.

Closes #1811
2014-12-12 16:17:34 -08:00
Moxie Marlinspike
15f44a78a9 Update min/max fix in shortcutbadger
// FREEBIE
2014-12-12 16:12:49 -08:00
Moxie Marlinspike
6b05f7905b Use betterpicker for HMS timeout.
// FREEBIE

Closes #1890
2014-12-12 16:07:02 -08:00
McLoo
9beb51bd77 Enabled audio and video drafts
//FREEBIE

Closes #1799
2014-12-12 14:44:21 -08:00
agrajaghh
0b62cf372b whitespace cleanup
//FREEBIE

Closes #1779
2014-12-12 14:39:51 -08:00
Moxie Marlinspike
b7badec752 Switch to forked ShortcutBadger.
// FREEBIE
2014-12-12 11:49:48 -08:00
Jake McGinty
a5e5a4c37f update build tools
// FREEBIE
2014-12-12 10:06:15 -08:00
Jake McGinty
c448a833f5 another minor pref tweak for mms fallback
1) fix summary for outgoing sms/mms
2) disable mms option when TS-users is unselected, same as the fallback
   approval option.

// FREEBIE
2014-12-11 22:46:59 -08:00
Jake McGinty
761049d6f8 minor preferences tweaks
1) shorten some summaries
2) add summaries to appearance subscreen
3) move common summary functions to common parent class

// FREEBIE
2014-12-11 22:12:12 -08:00
Moxie Marlinspike
df12174458 Add support for notification reminders.
// FREEBIE

Closes #1623
Fixes #323
2014-12-11 19:38:46 -08:00
agrajaghh
6e7566f781 remove workaround for nested PreferenceScreen's
Closes #2208
//FREEBIE
2014-12-11 19:36:15 -08:00
Jake McGinty
30232c1635 transport selection refactor
Closes #1724
// FREEBIE
2014-12-11 19:27:24 -08:00
Bastian Köcher
4a088410ae "add contact" button for unknown contacts
Closes #1551
2014-12-11 18:59:29 -08:00
Jake McGinty
b05967f2c9 Show drafts in conversation list snippets
// FREEBIE
2014-12-11 18:27:54 -08:00
Jabro
1d0c9536fc Support for ShortcutBadger
// FREEBIE

Closes #1574
Closes #1952

Fixes #1526
Fixes #321
2014-12-11 17:23:21 -08:00
Moxie Marlinspike
3c4ac4b105 Add option that allows disabling MMS fallback with TS users.
// FREEBIE

Closes #1444
2014-12-11 16:12:36 -08:00
3xo
ff05642f3e Enable progress indicator on group update
Closes #1165
2014-12-11 16:05:07 -08:00
Moxie Marlinspike
4e98c350a5 Support localization in passphrase activity and lock notification.
// FREEBIE

Closes #1328
2014-12-11 15:04:32 -08:00
Lukas Barth
e5e5b93884 Add k-9 style avatars to group conversations
Closes #1107
2014-12-11 14:40:44 -08:00
agrajaghh
2f6cefca8a refactor preferences into multiple PreferenceFragments 2014-12-11 14:02:28 -08:00
Sam Lanning
8149af22ec Open Keyboard When NewConversationActivity opened
// FREEBIE
2014-12-11 12:40:21 -08:00
agrajaghh
55ee288c36 Refactor notification for encrypted messages.
Closes #1184
2014-12-11 12:13:08 -08:00
Moxie Marlinspike
1833e57c38 Break libaxolotl out into an external project.
// FREEBIE
2014-12-08 16:07:16 -08:00
Moxie Marlinspike
c14945791c gradle tools 1.0.0
// FREEBIE
2014-12-08 14:17:09 -08:00
Moxie Marlinspike
b914d5ca25 Bump version to 2.3.3
// FREEBIE
2014-12-04 17:35:03 -08:00
Moxie Marlinspike
7411a49572 Updated language translations.
// FREEBIE
2014-12-04 16:44:31 -08:00
Mladen Pejaković
3fcd2bb88d Add Croatian language to list
// FREEBIE

Closes #2167
Fixes #2026
2014-12-04 16:02:42 -08:00
Moxie Marlinspike
978f769d6d Notify on incoming messages when locked.
// FREEBIE

Fixes #2149
2014-12-04 15:49:09 -08:00
Moxie Marlinspike
9d693eef30 Fix for fallback behavior.
// FREEBIE
2014-12-03 14:16:34 -08:00
Moxie Marlinspike
8f79ba1dd1 Merge pull request #2178 from mcginty/async-media-preview
media preview fixes
2014-12-03 09:09:43 -08:00
Jake McGinty
15613894f0 load image previews asynchronously
// FREEBIE
2014-12-03 13:03:03 +00:00
Jake McGinty
a88fbba49f use Loader when conversation is updated instead of requery
// FREEBIE
2014-12-03 04:47:38 +00:00
Jake McGinty
f3f173f653 support mms basic auth
// FREEBIE
2014-12-02 08:59:43 +00:00
Moxie Marlinspike
5002a749cf Merge pull request #2166 from agrajaghh/building.md_ndk_path
Building.md: library -> libaxolotl
2014-12-01 09:06:32 -08:00
agrajaghh
4a107483c1 path changed: library -> libaxolotl 2014-12-01 16:09:55 +01:00
Tal Hacohen
9ef1ea283b Fix notification display order.
Messages in notifications were showing in reverse order,
that is newest on top instead of newest at the bottom making
multiple messages hard to read.

Closes #1984
2014-11-30 08:35:51 -08:00
christian mock
32153ec379 Use "sms_body" extra from Intent
Closes #2071
2014-11-29 19:31:25 -08:00
Mladen Pejaković
ceb82a05ca Add Serbian language to the list
Closes #2008
2014-11-29 13:43:51 -08:00
McLoo
899bf8160d Cleanup of unused strings in strings.xml
//FREEBIE

Closes #2139
2014-11-29 13:35:36 -08:00
McLoo
49f77c9c06 Added l10n for message trimmer
//FREEBIE

Closes #2079
Closes #1868
2014-11-29 13:31:44 -08:00
McLoo
cd5feccdbf Fix selection for hungarian
// FREEBIE

Closes #2025
2014-11-29 13:19:49 -08:00
Moxie Marlinspike
a1da22cee9 Use constant.
// FREEBIE

Closes #1988
2014-11-29 13:12:53 -08:00
Jakob
a950db74c0 add dependencies to BUILDING.md
// FREEBIE

Closes #1809
2014-11-29 11:54:30 -08:00
Jake McGinty
b0891fa986 move log submission to new library
// FREEBIE

Closes #1655
2014-11-29 11:42:25 -08:00
Moxie Marlinspike
b57763f397 Display 'me' for device's local number in group membership list.
// FREEBIE

Fixes #1494
Closes #1505
2014-11-28 10:46:50 -08:00
Moxie Marlinspike
de75a98093 Move JobManager to external project and dependency.
// FREEBIE
2014-11-28 10:10:47 -08:00
Moxie Marlinspike
0d01feebc3 Move directory refresh from a service over to a job.
// FREEBIE
2014-11-27 15:24:26 -08:00
Moxie Marlinspike
0fb7f3b970 Minor cleanup.
// FREEBIE
2014-11-26 10:05:30 -08:00
agrajaghh
a9637baddf PushNetworkException for network errors on external downloads.
Closes #2112
Fixes #1264

// FREEBIE
2014-11-25 09:07:49 -08:00
Moxie Marlinspike
5e61161767 Bump version to 2.3.2
// FREEBIE
2014-11-24 23:44:50 -08:00
Jake McGinty
83d281de71 compat actionbar bg for dark theme
// FREEBIE
2014-11-25 09:28:22 +02:00
Jake McGinty
28f63cf335 Revert "Try to fix LGE menu button NPEs"
This reverts commit f3a3aaca60.
2014-11-25 09:23:57 +02:00
Jake McGinty
f3a3aaca60 Try to fix LGE menu button NPEs
fix based on https://code.google.com/p/android/issues/detail?id=78154

// FREEBIE
2014-11-25 09:17:27 +02:00
agrajaghh
908e8b8746 fix for ringtone selection with android-support-v4-preferencefragment 2014-11-25 09:08:30 +02:00
Jake McGinty
4573f3cb77 reorder window feature requests in MediaPreviewActivity
ActionBar is initialized in a different way by AppCompat v20 compared to
ABS or v21 even...
// FREEBIE
2014-11-25 08:55:40 +02:00
Moxie Marlinspike
d72a3ec669 Do thread trimming as JobManager job rather than AsyncTask.
// FREEBIE
2014-11-24 22:50:32 -08:00
Moxie Marlinspike
3439657bba Guard against fragment disappearing before callback.
// FREEBIE
2014-11-24 22:48:50 -08:00
Moxie Marlinspike
1ad7912e75 Add callback method parameter to axolotl decrypt functions.
// FREEBIE
2014-11-24 12:17:15 -08:00
Moxie Marlinspike
30aff82341 The future is the other direction.
// FREEBIE
2014-11-24 11:37:28 -08:00
Moxie Marlinspike
c54a5b613b Bump version to 2.3.1
// FREEBIE
2014-11-24 11:15:29 -08:00
Moxie Marlinspike
31ad30b3a5 Updated translations.
// FREEBIE
2014-11-24 10:59:03 -08:00
Moxie Marlinspike
4ccb7ebab6 Don't completely disable GCM.
// FREEBIE
2014-11-24 10:48:33 -08:00
Jake McGinty
bf69a90b69 rollback to appcompat v20
v21 appears to just not be ready for production given the bugs.
// FREEBIE
2014-11-24 19:09:33 +02:00
Moxie Marlinspike
bf19307916 Make GCM re-registration failure notification based.
// FREEBIE
2014-11-23 17:12:35 -08:00
Moxie Marlinspike
d42c4229fd Update to latest zxing QR code scanner.
Closes #2078

// FREEBIE
2014-11-22 09:47:02 -08:00
Moxie Marlinspike
31eddbf346 Bump version to 2.3.0
// FREEBIE
2014-11-21 14:49:14 -08:00
Moxie Marlinspike
e9b383d277 Add jobs for pending push messages during migration.
// FREEBIE
2014-11-21 12:53:40 -08:00
Moxie Marlinspike
eeafb81c90 Fix escaping in danish translation.
// FREEBIE
2014-11-21 12:53:24 -08:00
Moxie Marlinspike
02e27c94f2 Update language translations.
// FREEBIE
2014-11-21 11:49:35 -08:00
Jake McGinty
167386ea49 make the light theme light
Fixes #2111
Tested on GB and LP
// FREEBIE
2014-11-20 21:59:15 -08:00
Jake McGinty
e31994ac77 proguard has guarded its last pro
// FREEBIE
2014-11-20 17:45:31 -08:00
Moxie Marlinspike
ea9a5decac Update gradle-witness and actually use spongycastle.
// FREEBIE
2014-11-20 16:46:35 -08:00
Moxie Marlinspike
1cb191a6ee Move targetSdkVersion back down to 19.
// FREEBIE
2014-11-20 15:58:32 -08:00
Jake McGinty
60737bdd7b proguard: don't warn for test classes
// FREEBIE
2014-11-19 14:52:02 -08:00
Moxie Marlinspike
2dded4888e No more push encoding pretense.
// FREEBIE
2014-11-19 14:45:12 -08:00
Jake McGinty
eaf89735b8 encapsulated delivery icon to separate from lock
and as a bonus some tweaked text sizes and colors
// FREEBIE
2014-11-19 12:56:44 -08:00
Jake McGinty
8fa2f92a91 use latest appcompat and build tools, add dagger pin
// FREEBIE
2014-11-18 17:03:10 -08:00
Moxie Marlinspike
174324e2a0 Potential fix for BroadcastReceiver crash.
// FREEBIE
2014-11-18 14:55:16 -08:00
Jake McGinty
bc3686058a optimize new assets
Thanks to @pejakm for pointing out the emoji optimization.
// FREEBIE
2014-11-18 11:27:10 -08:00
Jake McGinty
0110015c8b remove unused image resources
// FREEBIE
2014-11-18 11:27:10 -08:00
Jake McGinty
5ca8a6d421 materialize conversation item indicators
// FREEBIE
2014-11-18 11:27:08 -08:00
Jake McGinty
0fc9ff7490 add "elevation" window overlay for android 4.x too
// FREEBIE
2014-11-17 12:14:09 -08:00
Jake McGinty
08d939b010 fix AppCompat theme crash
Fixes #2084
// FREEBIE
2014-11-17 11:51:49 -08:00
Jake McGinty
020920d988 silly actionbar shadow issue
// FREEBIE
2014-11-17 11:28:57 -08:00
Moxie Marlinspike
bd3d9ac533 Update JobManager README.md
// FREEBIE
2014-11-17 09:17:14 -08:00
agrajaghh
85670d95ee Fix README.md formatting
// FREEBIE

Closes #2101
2014-11-17 08:57:21 -08:00
Moxie Marlinspike
3d1007d101 Added README
// FREEBIE
2014-11-16 17:24:05 -08:00
Moxie Marlinspike
35821d444e Move responsibility for Context injection out of JavaSerializer.
// FREEBIE
2014-11-16 17:23:33 -08:00
Moxie Marlinspike
3cd7c2d8e5 libjobqueue javadoc and scoping.
// FREEBIE
2014-11-16 15:53:51 -08:00
Moxie Marlinspike
5b08791086 Fix regression with providers being registered.
// FREEBIE
2014-11-14 15:44:49 -08:00
Jake McGinty
31b9dcb5eb stop crashes when sending sms
// FREEBIE
2014-11-15 02:14:37 +03:00
Jake McGinty
43adc75428 add icon back
// FREEBIE
2014-11-15 02:04:17 +03:00
Jake McGinty
67a4523ca7 update gradle+plugins, update gradle-witness
// FREEBIE
2014-11-14 13:07:04 +03:00
Moxie Marlinspike
b35b9be0c8 Add missing copyright headers from libtextsecure.
// FREEBIE
2014-11-12 18:37:16 -08:00
Moxie Marlinspike
9215322846 Abstract out TrustStore interface.
// FREEBIE
2014-11-12 17:09:59 -08:00
Moxie Marlinspike
cbebc040cc Make ProGuard with with Dagger 2014-11-12 16:03:58 -08:00
Moxie Marlinspike
bea26e83da Correctly process push messages with identity key conflicts. 2014-11-12 15:42:43 -08:00
Moxie Marlinspike
a85dbce041 Correctly handle PKWM via SMS. 2014-11-12 15:42:43 -08:00
Moxie Marlinspike
baaa3514d4 Fix delivery receipts in group messages.
Fixes #2056
Fixes #2067
Fixes #2087
2014-11-12 15:42:43 -08:00
Moxie Marlinspike
71fdaac1b2 Fix regressions for registration. 2014-11-12 15:42:43 -08:00
Moxie Marlinspike
fb31319e52 Put everything under either internal or api. 2014-11-12 15:42:43 -08:00
Moxie Marlinspike
08ed90c5ec Split out Util functions. 2014-11-12 15:42:43 -08:00
Moxie Marlinspike
0d102f76cc Move ListenableFutureTask up to parent. 2014-11-12 15:38:23 -08:00
Moxie Marlinspike
28cb1ed85b Move DirectoryUtil up to parent. 2014-11-12 15:29:59 -08:00
Moxie Marlinspike
cd9b20dc9d Move dependency up to parent. 2014-11-12 15:29:59 -08:00
Moxie Marlinspike
f09abff407 Refactor out old classes. 2014-11-12 15:29:59 -08:00
Moxie Marlinspike
f9934bd8e5 Modernize libtextsecure layout 2014-11-12 15:29:59 -08:00
Moxie Marlinspike
1182052c7f Rename library to libtextsecure 2014-11-12 15:29:58 -08:00
Moxie Marlinspike
0d06d50a65 Let's have JobManager only deal with checked exceptions.
Also, switch to Builder for JobManager construction.
2014-11-12 15:29:58 -08:00
Moxie Marlinspike
d9d4ec9d9d Fix some bugs with PKWM padding and attachment detection. 2014-11-12 15:29:58 -08:00
Moxie Marlinspike
9a6f65988f Add support for dependency injection, and accompanying tests. 2014-11-12 15:29:58 -08:00
Moxie Marlinspike
601e233d47 Add account management interface to libtextsecure api 2014-11-12 15:28:08 -08:00
Moxie Marlinspike
ae178fc4ec Move API around a little, eliminate TransportDetails interface. 2014-11-12 15:26:25 -08:00
Moxie Marlinspike
cafe03a70a Transition the outbound pipeline to JobManager jobs. 2014-11-12 15:26:25 -08:00
Moxie Marlinspike
99f42e2ee1 Move API around. 2014-11-12 15:21:32 -08:00
Moxie Marlinspike
a3f1d9cdfd Beginning of libtextsecure refactor.
1) Break out appropriate components.

2) Switch the incoming pipeline from SendReceiveService to
   the JobManager.
2014-11-12 15:21:32 -08:00
Jake McGinty
4cab657ebe clear pending slides when attachment reselected
Fixes #2012

// FREEBIE
2014-10-29 18:50:11 -07:00
Jake McGinty
db6f8618e6 padding workaround for bug in appcompat-v7 21.0.0
bug: https://code.google.com/p/android/issues/detail?id=77982

// FREEBIE
2014-10-29 18:28:20 -07:00
Jake McGinty
98af1fb6ee shorten blocks of text
// FREEBIE
2014-10-29 16:53:25 -07:00
Jake McGinty
ad1d55f12d enable proguard
// FREEBIE
2014-10-29 16:53:21 -07:00
Jake McGinty
7df49811b7 replace ABS with AppCompat
// FREEBIE
2014-10-29 16:51:55 -07:00
Jake McGinty
ff2ac8a66e refactor ListenableFutureTask and make saves async
// FREEBIE
2014-10-28 02:25:41 -05:00
Jake McGinty
53da1f849a in-app image media preview
// FREEBIE
2014-10-28 00:50:01 -05:00
Moxie Marlinspike
503d1ef452 Bump version to 2.2.0
// FREEBIE
2014-10-24 16:51:15 -07:00
Moxie Marlinspike
9accd92757 Updated language translations.
// FREEBIE
2014-10-24 13:18:52 -07:00
agrajaghh
306da92031 Test case and fix for deferred jobs.
Closes #2033
2014-10-24 12:08:03 -07:00
Moxie Marlinspike
e7b6a852c5 Fix bug caused by 1mod8 compatibility issue.
In the switch from v3, we bind identities in the message MAC
instead of doing the 1mod8 trick.  Since identity keys were
never set as 1mod8, it seemed like we could just remove it.

However, PreKeys are durable.  If an old client upgrades to v3,
it has a bunch of keys that *were* set to 1mod8 floating around.
The Curve25519 donna code re-sets the private key bits on every
operation, which results in a different key, and breaks the output
of an agreement.

So now we don't intentionally generate keys with 1mod8, but we
have to remove the donna code to honor existing 1mod8 keys for
the rest of time.  Trevor is squarely to blame.

// FREEBIE
2014-10-23 17:11:46 -07:00
Moxie Marlinspike
9dfaf19516 Merge pull request #2028 from mcginty/revert-generic-contenttype
revert content-type generics
2014-10-22 19:37:45 -07:00
Jake McGinty
34aece0b43 revert content-type generics
// FREEBIE
2014-10-22 19:37:04 -07:00
Moxie Marlinspike
9768de2d5e Short circuit self-send.
// FREEBIE
2014-10-22 18:28:03 -07:00
Moxie Marlinspike
c3eb0ea9db Check job requirements are satisfied in between retry iterations.
// FREEBIE
2014-10-21 13:50:38 -07:00
Moxie Marlinspike
4cdc0a3e61 Make signed prekey generation happen through the job queue. 2014-10-21 12:47:08 -07:00
Moxie Marlinspike
b568ce70b2 Make delivery receipts work correctly for groups. 2014-10-20 21:06:34 -07:00
Moxie Marlinspike
18b0601990 Dependency updates and gradle housekeeping. 2014-10-20 21:06:09 -07:00
Moxie Marlinspike
b308996885 Switch to using our own JobManager.
// FREEBIE
2014-10-20 19:13:06 -07:00
Moxie Marlinspike
73d896f378 Index shouldn't be unique. 2014-10-20 19:02:42 -07:00
Moxie Marlinspike
36ec1d84a1 Implement delivery receipts.
1) Support a "receipt" push message type.

2) Identify messages by timestamp.

3) Introduce a JobManager to handle the queue for network
   dependent jobs.
2014-10-20 19:02:42 -07:00
Moxie Marlinspike
8d6b9ae43e Incorporate PR feedback. Add license and eliminate duplicate code.
// FREEBIE
2014-10-20 18:38:52 -07:00
Moxie Marlinspike
a95cc0eba2 Switch to byte array.
// FREEBIE
2014-10-20 18:35:45 -07:00
Moxie Marlinspike
58d101ff2e Support for job "group ids."
A job can specify a group id, and jobs with the same group id
will run sequentially.
2014-10-20 18:35:45 -07:00
Moxie Marlinspike
544f06451f Persistent job queue, derivative of android-priority-jobqueue.
// FREEBIE
2014-10-20 18:35:45 -07:00
Moxie Marlinspike
20cf775b1e Fix up routing activity actions. 2014-10-20 14:55:34 -07:00
Moxie Marlinspike
5fcc135f81 Make sure senderkeys encrypt is correctly initialized. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
9a0ed659f7 Initial support for sender keys. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
54612159be Update ed25519 extract and tests 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
355d0be78a Introduce new simultaneous initiate strategy.
1) Fix bugs that prevented decrypt() from being non-transactional
   in some cases.

2) Introduce a new unified storage interface.

3) Transition simultaneous initiate from the "needs refresh"
   strategy to one that uses session state resurrection and
   promotion.
2014-10-20 12:25:40 -07:00
Moxie Marlinspike
73b75a4a27 Fix build. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
c4209a65e3 Don't assert on bad padding. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
9dce376780 Correctly handle formatting when "one time PreKey" is absent. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
07c61394e9 When processing PreKeyBundle, archive current session if it exists. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
3e287f930d Better thread safety for session building <-> use. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
7b1a37bd91 Make registration ID optionally extended. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
2db44a1578 Make generated PreKeyIds exclude both 0 and Medium.MAX_VALUE. 2014-10-20 12:25:40 -07:00
Moxie Marlinspike
006c9aae7b Only remove unsigned prekey if bundled message decrypts properly. 2014-10-20 12:25:39 -07:00
Moxie Marlinspike
b147a90463 This exception is never thrown. 2014-10-20 12:25:39 -07:00
Moxie Marlinspike
741171c49f Switch to CBC mode with a derived IV.
1) Since we're not CPU or space constrained (and are in fact
   padding), and since keystream reuse would be more catastrophic
   than IV reuse without chosen plaintext.
2014-10-20 12:25:39 -07:00
Moxie Marlinspike
c375ed8638 MIPS NDK support. Apparently there are mips devices... 2014-10-20 12:25:39 -07:00
Moxie Marlinspike
238f29c90a Updated to latest of Trevor's ref10-extract 2014-10-20 12:25:39 -07:00
Moxie Marlinspike
084f27a2e8 omg trevor 2014-10-20 12:25:39 -07:00
Moxie Marlinspike
27b5bf54cc Remove 1 mod 8. 2014-10-20 12:25:39 -07:00
Moxie Marlinspike
eda393b11c Minor refactoring and renaming. 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
c330eef7b9 Make PreKeyWhisperMessage decrypt more reliably atomic. 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
1eb3884b7a Update to latest ref10-extract ed25519 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
5ea3b3038e Remove verification tag.
1) Remove verification tag from PreKeyWhisperMessage.

2) Include sender and recipient identity keys in the MAC of
   each WhisperMessage.
2014-10-20 12:25:38 -07:00
Moxie Marlinspike
641ac9aed9 Rename axolotl terminology.
1) ephemeralKey -> ratchetKey

2) Have the caller specify Alice/Bob orientation.

3) Reorganize verification tag.

4) Remove verification tag from key exchange messages, replace
   with signatures in both directions.
2014-10-20 12:25:38 -07:00
Moxie Marlinspike
82bd75fb75 Fix padding problem. 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
c94a7b1eff Make sure "previous counter" is never negative. 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
4caebdcd06 Update tests for new API. 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
b3cece27d6 Update SessionCipher javadocs. 2014-10-20 12:25:38 -07:00
Moxie Marlinspike
819982af7b Rearrange decrypt API.
1) Change SessionBuilder to only establish sessions via
   KeyExchangeMessage and PreKeyBundles.

2) Change SessionCipher to decrypt either WhisperMessage
   or PreKeyWhisperMessage items, automatically building
   a session for the latter.

3) Change SessionCipher to tear down new sessions built
   with PreKeyWhisperMessages if the embedded WhsiperMessage
   fails to decrypt.
2014-10-20 12:25:38 -07:00
Moxie Marlinspike
42cf53e487 Rename "pendingPreKey" to "unacknowledgedPreKeyMessage" 2014-10-20 12:23:15 -07:00
Moxie Marlinspike
e0d2398ca5 Rename InitializationParameters -> AxolotlParameters 2014-10-20 12:23:14 -07:00
Moxie Marlinspike
3f299936bf Only create signed prekey if push registered. 2014-10-20 12:23:14 -07:00
Moxie Marlinspike
540592d71f Upgrade libaxolotl to the latest gradle plugin. 2014-10-20 12:23:14 -07:00
Moxie Marlinspike
5a9e5672d3 Updated README 2014-10-20 12:23:11 -07:00
Moxie Marlinspike
0a23b5fcd5 Added helper method for generating signed PreKeys. 2014-10-20 12:23:10 -07:00
Moxie Marlinspike
f0c22d593f Simplify/clarify internal interfaces and introduce optional types. 2014-10-20 12:23:10 -07:00
Moxie Marlinspike
5f5ddd7c26 Generate SignedPreKey records, improve SignedPreKey cleanup. 2014-10-20 12:23:08 -07:00
Moxie Marlinspike
144f269059 Upgrade curve25519-donna to latest. 2014-10-20 12:17:24 -07:00
Moxie Marlinspike
0d532afd8e Rename 'device key' to 'signed prekey'. 2014-10-20 12:17:24 -07:00
Moxie Marlinspike
07fd17ccda Add padding for push messages.
1) Use 'bit padding.'

1) By default, pad at 160 byte increments.
2014-10-20 12:17:23 -07:00
Moxie Marlinspike
fcaa3f0d73 Simplify HKDF interface. 2014-10-20 12:17:23 -07:00
Moxie Marlinspike
64b40df15b Add V3 support for KeyExchangeMessage case.
1) V3 KeyExchangeMessages can now contain signatures and
   verification tags.
2014-10-20 12:17:23 -07:00
Moxie Marlinspike
77ff9cece8 Add a 'verification tag' to incoming PreKeyWhisperMessage bundles. 2014-10-20 12:17:23 -07:00
Moxie Marlinspike
6326ef73f3 Split HKDF secret derivation and parsing. 2014-10-20 12:17:23 -07:00
Moxie Marlinspike
f29d1e6269 Add support for a compliant HKDF implementation. 2014-10-20 12:17:23 -07:00
Moxie Marlinspike
d6c5e92c9d Collapse RatchetingSessionV2 and RatchetingSessionV3. 2014-10-20 12:17:23 -07:00
Moxie Marlinspike
811479d168 Add first cut of protocol v3 support.
1) Use the new /v2/keys API for storing/retrieving prekey bundles.

2) For sessions built with PreKeyBundle and PreKeyWhisperMessage,
   use a v3 ratcheting session when available.
2014-10-20 12:17:23 -07:00
Moxie Marlinspike
2ed8d333d9 Add ed25519 2014-10-20 12:14:18 -07:00
Moxie Marlinspike
79020cd33c Better FS Locking. 2014-10-20 12:14:18 -07:00
Moxie Marlinspike
c8757c2134 Make helper static. 2014-10-20 12:14:18 -07:00
Moxie Marlinspike
2a65257182 Add serialization helpers for IdentityKeyPair. 2014-10-20 12:14:18 -07:00
Moxie Marlinspike
931605a1c4 Move identity key verification into libaxolotol. With tests. 2014-10-20 12:14:18 -07:00
Moxie Marlinspike
81ae9af2e4 Add "last resort" PreKey generation to KeyHelper. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
e84a0948e9 Update README 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
5239b3e8f8 Add basic README. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
a601c56af1 Collapse SessionRecord, SessionState, and PreKeyRecord interfaces. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
5a3c19fe3e Javadocs, and some minor refactoring. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
af45e5d544 SessionBuilder improvements, more extensive SessionBuilder tests. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
72af8b11c2 Move session construction and KeyExchangeMessage into libaxolotl.
1) Add plain two-way key exchange support libaxolotl by moving
   all the KeyExchangeMessage code there.

2) Move the bulk of KeyExchangeProcessor code to libaxolotl
   for setting up sessions based on retrieved prekeys, received
   prekeybundles, or exchanged key exchange messages.
2014-10-20 12:14:17 -07:00
Moxie Marlinspike
a1db221caf Collapse KeyExchangeMessage and KeyExchangeProcessor interfaces. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
14b8f97de2 Reorganize session store load/store operations. 2014-10-20 12:14:17 -07:00
Moxie Marlinspike
d902c12941 Break core ratchet out into libaxolotol.
1) Break the core cryptography functions out into libaxolotol.

2) The objective for this code is a Java library that isn't
   dependent on any Android functions.  However, while the
   code has been separated from any Android functionality,
   it is still an 'android library project' because of the
   JNI.
2014-10-20 12:10:02 -07:00
Moxie Marlinspike
fe3d91c40c Bump version to 2.1.10
// FREEBIE
2014-10-18 14:19:45 -07:00
Jake McGinty
31167d11dd use wildcard media types, don't crash on preview fail
// FREEBIE
2014-10-17 17:59:56 -07:00
Jake McGinty
6e3751a0c5 sane fallback for media selection on exception
Fixes #1763
// FREEBIE
2014-10-17 17:41:42 -07:00
Jake McGinty
b5941fb3fd actually fallback when mms local params unset
// FREEBIE
2014-10-15 17:14:12 -07:00
Moxie Marlinspike
cfccd367ad Bump version to 2.1.9
// FREEBIE
2014-10-13 19:02:37 -07:00
Moxie Marlinspike
2c8c6a410c Updated language translations.
// FREEBIE
2014-10-13 18:11:20 -07:00
Jake McGinty
2075bba86c switch back to BufferedInputStream
// FREEBIE
2014-10-13 15:38:27 -07:00
Jake McGinty
1d7b47c982 add CM APN list, remove non-MMS APN info
Fixes missing Verizon APN. Overall, smaller DB size but more APN info.
// FREEBIE
2014-10-13 12:33:54 -07:00
Jake McGinty
89fb80fcc5 MmsConnection refactor
- Use Apache HttpClient v4.x, only library that seems to like HTTP proxies
- Remove custom redirect logic in favor of library's

Fixes #1904
// FREEBIE
2014-10-13 12:26:38 -07:00
Moxie Marlinspike
f1d230ce6e Merge pull request #1964 from mcginty/maven-https
use https maven central
2014-10-01 17:16:11 -07:00
Jake McGinty
f51a5aa7eb use https maven central
// FREEBIE
2014-09-30 21:46:13 -07:00
Moxie Marlinspike
cf9c9bbfd7 Bumping version to 2.1.8
// FREEBIE
2014-09-15 19:34:30 -07:00
Jake McGinty
b8a3e87f3d custom redirect logic
// FREEBIE
2014-09-15 14:13:27 -07:00
Moxie Marlinspike
e5bad2746f Merge pull request #1917 from mcginty/mccmnc-crash
don't crash if we can't get an mccmnc
2014-09-15 09:32:28 -07:00
Jake McGinty
7316f17674 don't crash if we can't get an mccmnc
Fixes #1916
// FREEBIE
2014-09-14 17:46:07 -07:00
Veeti Paananen
609e69a801 Show date and year for ancient (> 1 year old) messages
The timestamp shown for very old messages only includes the time without
a date at all. Fix it. FREEBIE.
2014-09-08 15:08:46 -07:00
1098 changed files with 33900 additions and 67856 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.ai binary

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,tr_TR:tr,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,tr_TR:tr,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs,sr:sr,he:iw,id:in
[textsecure-official.master]

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms"
android:versionCode="79"
android:versionName="2.1.7">
android:versionCode="105"
android:versionName="2.9.2">
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
@@ -37,7 +37,7 @@
android:protectionLevel="signature" />
<uses-permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE" />
<application android:name="org.thoughtcrime.securesms.ApplicationListener"
<application android:name=".ApplicationContext"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/TextSecure.LightTheme">
@@ -90,7 +90,18 @@
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MmsPreferencesActivity"
<activity android:name=".DeviceProvisioningActivity"
android:theme="@style/TextSecure.DialogActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tsdevice"/>
</intent-filter>
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ShareActivity"
@@ -107,19 +118,26 @@
<activity android:name=".ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MessageDetailsActivity"
android:label="Message Details"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".GroupCreateActivity"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseMigrationActivity"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseUpgradeActivity"
android:theme="@style/NoAnimation.Theme.Sherlock.Light.DarkActionBar"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -137,14 +155,9 @@
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".NewConversationActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
@@ -152,11 +165,6 @@
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".AutoInitiateActivity"
android:theme="@style/TextSecure.Light.Dialog"
android:label="@string/AndroidManifest__textsecure_detected"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ViewIdentityActivity"
android:label="@string/AndroidManifest__public_identity_key"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -173,7 +181,6 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ReceiveKeyActivity"
android:label="@string/AndroidManifest__complete_key_exchange"
android:theme="@style/TextSecure.Light.Dialog"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -192,6 +199,16 @@
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaOverviewActivity"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
android:enabled="true"
@@ -203,13 +220,16 @@
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
<service android:enabled="true" android:name=".service.GcmRegistrationService"/>
<activity android:name=".PlayServicesProblemActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.SendReceiveService"/>
<service android:enabled="true" android:name=".service.RegistrationService"/>
<service android:enabled="true" android:name=".service.DirectoryRefreshService"/>
<service android:enabled="true" android:name=".service.PreKeyService"/>
<service android:enabled="true" android:name=".service.MessageRetrievalService"/>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
@@ -285,6 +305,10 @@
android:grantUriPermissions="true"
android:authorities="org.thoughtcrime.provider.securesms" />
<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:authorities="org.thoughtcrime.provider.securesms.mms" />
<receiver android:name=".service.RegistrationNotifier"
android:exported="false">
<intent-filter>
@@ -299,6 +323,17 @@
</intent-filter>
</receiver>
<uses-library android:name="android.test.runner" />
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.MessageNotifier$DeleteReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.MessageNotifier.DELETE_REMINDER_ACTION"/>
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -17,7 +17,11 @@ The following steps should help you (re)build TextSecure from the command line.
git clone https://github.com/WhisperSystems/TextSecure.git
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed somewhere on your system.
3. Ensure the "Android Support Repository" and "Android SDK Build-tools" are installed from the Android SDK manager.
3. Ensure that the following packages are installed from the Android SDK manager:
* Android SDK Build Tools
* SDK Platform
* Android Support Repository
* Google Repository
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it.
sdk.dir=\<path to your sdk installation\>
@@ -26,19 +30,17 @@ The following steps should help you (re)build TextSecure from the command line.
./gradlew build
Re-building native components
-----------------------------
Visual assets
----------------------
Note: This step is optional; native components are contained as binaries (see [library/libs](library/libs)).
Source assets tend to be large binary blobs, which are best stored outside of git repositories. We host ours in a [Pixelapse repository](https://www.pixelapse.com/openwhispersystems/projects/signal-android/). Some source files are SVGs that can be auto-colored and sized using a tool like [android-res-utils](https://github.com/sebkur/android-res-utils).
1. Ensure that the Android NDK is installed.
Sample command for generating our audio placeholder image:
Execute ndk-build:
cd library
ndk-build
Afterwards, execute Gradle as above to re-create the APK.
```bash
pngs_from_svg.py ic_audio.svg /path/to/TextSecure/res/ 150 "#000" 0.54 _light
pngs_from_svg.py ic_audio.svg /path/to/TextSecure/res/ 150 "#fff" 1.0 _dark
```
Setting up a development environment
------------------------------------

View File

@@ -1,6 +1,6 @@
# TextSecure [![Build Status](https://travis-ci.org/WhisperSystems/TextSecure.svg?branch=master)](https://travis-ci.org/WhisperSystems/TextSecure)
TextSecure is a messaging app for easy private communicate with friends.
TextSecure is a messaging app for simple private communication with friends.
TextSecure can use either data (WiFi/3G/4G) or SMS to communicate securely, and all TextSecure
messages can also be encrypted locally on your device.
@@ -14,6 +14,16 @@ We use GitHub for bug tracking. Please search the existing issues for your bug a
https://github.com/WhisperSystems/TextSecure/issues
## Joining the Beta
Want to live life on the bleeding edge and help out with testing?
You can subscribe to TextSecure Beta releases in two steps:
1. Join the [TextSecure Beta Google+ Community](https://plus.google.com/communities/114424213916773497091).
1. After you've joined the community, [subscribe to the beta](https://play.google.com/apps/testing/org.thoughtcrime.securesms).
If you're interesting a life of peace and tranquility, stick with the standard releases.
## Contributing Translations
Interested in helping to translate TextSecure? Contribute here:
@@ -38,7 +48,7 @@ whispersystems@lists.riseup.net
https://lists.riseup.net/www/info/whispersystems
## Contributing Funds
[![Donate](https://coinbase.com/assets/buttons/donation_large-6ec72b1a9eec516944e50a22aca7db35.png)](https://whispersystems.org/blog/bithub/)
[![Donate](https://www.coinbase.com/assets/buttons/donation_large-36ee936185fdf9a88e3a28cc685fb9b7.png)](https://coinbase.com/checkouts/d29fd4c37ca442393e32fdcb95304701)
You can add funds to BitHub to directly help further development efforts.

View File

@@ -0,0 +1,24 @@
package org.thoughtcrime.securesms;
import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static android.support.test.espresso.assertion.ViewAssertions.*;
import android.test.suitebuilder.annotation.LargeTest;
@LargeTest
public class RegistrationActivityTest extends RoutedInstrumentationTestCase {
private final static String TAG = RegistrationActivityTest.class.getSimpleName();
public RegistrationActivityTest() {
super();
}
@SuppressWarnings("unchecked")
public void testRegistrationButtons() throws Exception {
waitOn(RegistrationActivity.class);
onView(withId(R.id.registerButton)).check(matches(isDisplayed()));
onView(withId(R.id.skipButton)).check(matches(isDisplayed())).perform(click());
}
}

View File

@@ -0,0 +1,37 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.app.Instrumentation.ActivityMonitor;
import android.preference.PreferenceManager;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
public class RoutedInstrumentationTestCase extends ActivityInstrumentationTestCase2<RoutingActivity> {
private static final String TAG = RoutedInstrumentationTestCase.class.getSimpleName();
public RoutedInstrumentationTestCase() {
super(RoutingActivity.class);
}
@Override
public void setUp() throws Exception {
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
super.setUp();
clearSharedPrefs();
getActivity();
}
protected void clearSharedPrefs() {
PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext())
.edit().clear().commit();
getInstrumentation().getTargetContext().getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
.edit().clear().commit();
}
protected static void waitOn(Class<? extends Activity> clazz) {
Log.w(TAG, "waiting for " + clazz.getName());
new ActivityMonitor(clazz.getName(), null, true).waitForActivityWithTimeout(10000);
}
}

View File

@@ -0,0 +1,16 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.test.InstrumentationTestCase;
public class TextSecureTestCase extends InstrumentationTestCase {
@Override
public void setUp() throws Exception {
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
}
protected Context getContext() {
return getInstrumentation().getContext();
}
}

View File

@@ -1,10 +1,10 @@
package org.thoughtcrime.securesms.database;
import android.test.InstrumentationTestCase;
import org.thoughtcrime.securesms.TextSecureTestCase;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
public class CanonicalAddressDatabaseTest extends InstrumentationTestCase {
public class CanonicalAddressDatabaseTest extends TextSecureTestCase {
private static final String AMBIGUOUS_NUMBER = "222-3333";
private static final String SPECIFIC_NUMBER = "+49 444 222 3333";
private static final String EMAIL = "a@b.fom";

View File

@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms.database;
import android.net.Uri;
import org.thoughtcrime.securesms.TextSecureTestCase;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.io.FileNotFoundException;
import java.io.InputStream;
import ws.com.google.android.mms.pdu.PduPart;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class PartDatabaseTest extends TextSecureTestCase {
private static final long PART_ID = 1L;
private PartDatabase database;
@Override
public void setUp() {
database = spy(DatabaseFactory.getPartDatabase(getInstrumentation().getTargetContext()));
}
public void testTaskNotRunWhenThumbnailExists() throws Exception {
when(database.getPart(eq(PART_ID))).thenReturn(getPduPartSkeleton("x/x"));
doReturn(mock(InputStream.class)).when(database).getDataStream(any(MasterSecret.class), anyLong(), eq("thumbnail"));
database.getThumbnailStream(null, PART_ID);
verify(database, never()).updatePartThumbnail(any(MasterSecret.class), anyLong(), any(PduPart.class), any(InputStream.class), anyFloat());
}
public void testTaskRunWhenThumbnailMissing() throws Exception {
when(database.getPart(eq(PART_ID))).thenReturn(getPduPartSkeleton("image/png"));
doReturn(null).when(database).getDataStream(any(MasterSecret.class), anyLong(), eq("thumbnail"));
doNothing().when(database).updatePartThumbnail(any(MasterSecret.class), anyLong(), any(PduPart.class), any(InputStream.class), anyFloat());
try {
database.new ThumbnailFetchCallable(mock(MasterSecret.class), PART_ID).call();
throw new AssertionError("didn't try to generate thumbnail");
} catch (FileNotFoundException fnfe) {
// success
}
}
private PduPart getPduPartSkeleton(String contentType) {
PduPart part = new PduPart();
part.setContentType(contentType.getBytes());
part.setDataUri(Uri.EMPTY);
return part;
}
}

View File

@@ -0,0 +1,154 @@
package org.thoughtcrime.securesms.jobs;
import android.test.AndroidTestCase;
import org.thoughtcrime.securesms.TextSecureTestCase;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.textsecure.api.TextSecureAccountManager;
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
public class CleanPreKeysJobTest extends TextSecureTestCase {
public void testSignedPreKeyRotationNotRegistered() throws IOException, MasterSecretJob.RequirementNotMetException {
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
MasterSecret masterSecret = mock(MasterSecret.class);
when(accountManager.getSignedPreKey()).thenReturn(null);
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
objectGraph.inject(cleanPreKeysJob);
cleanPreKeysJob.onRun(masterSecret);
verify(accountManager).getSignedPreKey();
verifyNoMoreInteractions(signedPreKeyStore);
}
public void testSignedPreKeyEviction() throws Exception {
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
SignedPreKeyEntity currentSignedPreKeyEntity = mock(SignedPreKeyEntity.class);
MasterSecret masterSecret = mock(MasterSecret.class);
when(currentSignedPreKeyEntity.getKeyId()).thenReturn(3133);
when(accountManager.getSignedPreKey()).thenReturn(currentSignedPreKeyEntity);
final SignedPreKeyRecord currentRecord = new SignedPreKeyRecord(3133, System.currentTimeMillis(), Curve.generateKeyPair(), new byte[64]);
List<SignedPreKeyRecord> records = new LinkedList<SignedPreKeyRecord>() {{
add(new SignedPreKeyRecord(2, 11, Curve.generateKeyPair(), new byte[32]));
add(new SignedPreKeyRecord(4, System.currentTimeMillis() - 100, Curve.generateKeyPair(), new byte[64]));
add(currentRecord);
add(new SignedPreKeyRecord(3, System.currentTimeMillis() - 90, Curve.generateKeyPair(), new byte[64]));
add(new SignedPreKeyRecord(1, 10, Curve.generateKeyPair(), new byte[32]));
}};
when(signedPreKeyStore.loadSignedPreKeys()).thenReturn(records);
when(signedPreKeyStore.loadSignedPreKey(eq(3133))).thenReturn(currentRecord);
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
objectGraph.inject(cleanPreKeysJob);
cleanPreKeysJob.onRun(masterSecret);
verify(signedPreKeyStore).removeSignedPreKey(eq(1));
verify(signedPreKeyStore, times(1)).removeSignedPreKey(anyInt());
}
public void testSignedPreKeyNoEviction() throws Exception {
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
SignedPreKeyEntity currentSignedPreKeyEntity = mock(SignedPreKeyEntity.class);
when(currentSignedPreKeyEntity.getKeyId()).thenReturn(3133);
when(accountManager.getSignedPreKey()).thenReturn(currentSignedPreKeyEntity);
final SignedPreKeyRecord currentRecord = new SignedPreKeyRecord(3133, System.currentTimeMillis(), Curve.generateKeyPair(), new byte[64]);
List<SignedPreKeyRecord> records = new LinkedList<SignedPreKeyRecord>() {{
add(currentRecord);
}};
when(signedPreKeyStore.loadSignedPreKeys()).thenReturn(records);
when(signedPreKeyStore.loadSignedPreKey(eq(3133))).thenReturn(currentRecord);
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
objectGraph.inject(cleanPreKeysJob);
verify(signedPreKeyStore, never()).removeSignedPreKey(anyInt());
}
public void testConnectionError() throws Exception {
SignedPreKeyStore signedPreKeyStore = mock(SignedPreKeyStore.class);
TextSecureAccountManager accountManager = mock(TextSecureAccountManager.class);
MasterSecret masterSecret = mock(MasterSecret.class);
when(accountManager.getSignedPreKey()).thenThrow(new PushNetworkException("Connectivity error!"));
CleanPreKeysJob cleanPreKeysJob = new CleanPreKeysJob(getContext());
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(accountManager, signedPreKeyStore));
objectGraph.inject(cleanPreKeysJob);
try {
cleanPreKeysJob.onRun(masterSecret);
throw new AssertionError("should have failed!");
} catch (IOException e) {
assertTrue(cleanPreKeysJob.onShouldRetry(e));
}
}
@Module(injects = {CleanPreKeysJob.class})
public static class TestModule {
private final TextSecureAccountManager accountManager;
private final SignedPreKeyStore signedPreKeyStore;
private TestModule(TextSecureAccountManager accountManager, SignedPreKeyStore signedPreKeyStore) {
this.accountManager = accountManager;
this.signedPreKeyStore = signedPreKeyStore;
}
@Provides TextSecureAccountManager provideTextSecureAccountManager() {
return accountManager;
}
@Provides
AxolotlStorageModule.SignedPreKeyStoreFactory provideSignedPreKeyStore() {
return new AxolotlStorageModule.SignedPreKeyStoreFactory() {
@Override
public SignedPreKeyStore create(MasterSecret masterSecret) {
return signedPreKeyStore;
}
};
}
}
}

View File

@@ -0,0 +1,100 @@
package org.thoughtcrime.securesms.jobs;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.thoughtcrime.securesms.TextSecureTestCase;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.textsecure.api.TextSecureMessageSender;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import org.whispersystems.textsecure.api.push.exceptions.NotFoundException;
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import dagger.Module;
import dagger.ObjectGraph;
import dagger.Provides;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
public class DeliveryReceiptJobTest extends TextSecureTestCase {
public void testDelivery() throws IOException {
TextSecureMessageSender textSecureMessageSender = mock(TextSecureMessageSender.class);
long timestamp = System.currentTimeMillis();
DeliveryReceiptJob deliveryReceiptJob = new DeliveryReceiptJob(getContext(),
"+14152222222",
timestamp, "foo");
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(textSecureMessageSender));
objectGraph.inject(deliveryReceiptJob);
deliveryReceiptJob.onRun();
ArgumentCaptor<TextSecureAddress> captor = ArgumentCaptor.forClass(TextSecureAddress.class);
verify(textSecureMessageSender).sendDeliveryReceipt(captor.capture(), eq(timestamp));
assertTrue(captor.getValue().getRelay().get().equals("foo"));
assertTrue(captor.getValue().getNumber().equals("+14152222222"));
}
public void testNetworkError() throws IOException {
TextSecureMessageSender textSecureMessageSender = mock(TextSecureMessageSender.class);
long timestamp = System.currentTimeMillis();
Mockito.doThrow(new PushNetworkException("network error"))
.when(textSecureMessageSender)
.sendDeliveryReceipt(any(TextSecureAddress.class), eq(timestamp));
DeliveryReceiptJob deliveryReceiptJob = new DeliveryReceiptJob(getContext(),
"+14152222222",
timestamp, "foo");
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(textSecureMessageSender));
objectGraph.inject(deliveryReceiptJob);
try {
deliveryReceiptJob.onRun();
throw new AssertionError();
} catch (IOException e) {
assertTrue(deliveryReceiptJob.onShouldRetry(e));
}
Mockito.doThrow(new NotFoundException("not found"))
.when(textSecureMessageSender)
.sendDeliveryReceipt(any(TextSecureAddress.class), eq(timestamp));
try {
deliveryReceiptJob.onRun();
throw new AssertionError();
} catch (IOException e) {
assertFalse(deliveryReceiptJob.onShouldRetry(e));
}
}
@Module(injects = DeliveryReceiptJob.class)
public static class TestModule {
private final TextSecureMessageSender textSecureMessageSender;
public TestModule(TextSecureMessageSender textSecureMessageSender) {
this.textSecureMessageSender = textSecureMessageSender;
}
@Provides TextSecureMessageSenderFactory provideTextSecureMessageSenderFactory() {
return new TextSecureMessageSenderFactory() {
@Override
public TextSecureMessageSender create(MasterSecret masterSecret) {
return textSecureMessageSender;
}
};
}
}
}

View File

@@ -4,11 +4,13 @@ import android.test.AndroidTestCase;
import junit.framework.AssertionFailedError;
import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import static org.fest.assertions.api.Assertions.assertThat;
import org.thoughtcrime.securesms.TextSecureTestCase;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
public class PhoneNumberFormatterTest extends AndroidTestCase {
import static org.assertj.core.api.Assertions.assertThat;
public class PhoneNumberFormatterTest extends TextSecureTestCase {
private static final String LOCAL_NUMBER = "+15555555555";
public void testFormatNumberE164() throws Exception, InvalidNumberException {

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -562,6 +562,7 @@
<apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="9" server="*" />
<apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="3" server="*" />
<apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="1" server="*" />
<apn mcc="310" mnc="000" carrier="Verizon CDMA HRPD" mmsc="http://mms.vzwreseller.com/servlets/mms" type="default,mms,hipri,dun,supl" authtype="3" mvno_type="spn" mvno_match_data="Tracfone" />
<apn mcc="310" mnc="4" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE" />
<apn mcc="310" mnc="4" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE" />
<apn mcc="310" mnc="4" carrier="Verizon CDMA HRPD" apn="CdmaNai" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,hipri,dun,stdhipri,supl" protocol="IPV4V6" bearer="6" carrier_enabled="FALSE" />
@@ -1717,8 +1718,6 @@
<apn mcc="434" mnc="4" carrier="Beeline-UZB MMS" apn="mms.beeline.uz" mmsc="http://mms" mmsproxy="172.30.30.166" mmsport="8080" type="mms" user="beeline" password="beeline" authtype="1" />
<apn mcc="434" mnc="5" carrier="UCELL MMS" apn="mms" mmsc="http://mmsc:8002/" mmsproxy="10.64.164.10" mmsport="8080" type="mms" />
<apn mcc="434" mnc="07" carrier="MTS-UZB MMS" apn="mms.mts.uz" mmsc="http://mmsc/was" mmsproxy="10.10.0.10" mmsport="8080" type="mms" />
<apn mcc="440" mnc="20" carrier="Application" apn="plus.acs.jp" mmsc="http://mms-s" mmsproxy="andmms.plusacs.ne.jp" mmsport="8080" type="default,mms,supl" user="plusw6q9tattkmpk" password="msfbbam83bsdetxb" authtype="2" />
<apn mcc="440" mnc="20" carrier="Application" apn="andoworld.softbank.ne.jp" mmsc="http://mms/" mmsproxy="andmms.softbank.ne.jp" mmsport="8080" type="default,mms,supl,hpri" authtype="0" />
<apn mcc="450" mnc="5" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" authtype="0" server="*" />
<apn mcc="450" mnc="5" carrier="SKT-Home" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms" authtype="0" server="*" />
<apn mcc="450" mnc="05" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="ia,default,mms,supl,fota,cbs" server="*" />

View File

@@ -1,19 +1,31 @@
import sys
import re
import argparse
import sqlite3
import gzip
from progressbar import ProgressBar, Counter, Timer
from lxml import etree
parser = argparse.ArgumentParser(prog='apntool', description="""Process Android's apn xml files and drop them into an easily
queryable SQLite db. Tested up to version 9 of their APN file.""")
parser.add_argument('-v', '--version', action='version', version='%(prog)s v1.0')
parser = argparse.ArgumentParser(prog='apntool', description="""Process Android's apn xml files and drop them into an
easily queryable SQLite db. Tested up to version 9 of
their APN file.""")
parser.add_argument('-v', '--version', action='version', version='%(prog)s v1.1')
parser.add_argument('-i', '--input', help='the xml file to parse', default='apns.xml', required=False)
parser.add_argument('-o', '--output', help='the sqlite db output file', default='apns.db', required=False)
parser.add_argument('--quiet', help='do not show progress or verbose instructions', action='store_true', required=False)
parser.add_argument('--no-gzip', help="do not gzip after creation", action='store_true', required=False)
args = parser.parse_args()
def normalized(target):
o2_typo = re.compile(r"02\.co\.uk")
port_typo = re.compile(r"(\d+\.\d+\.\d+\.\d+)\.(\d+)")
leading_zeros = re.compile(r"(/|\.|^)0+(\d+)")
subbed = o2_typo.sub(r'o2.co.uk', target)
subbed = port_typo.sub(r'\1:\2', subbed)
subbed = leading_zeros.sub(r'\1\2', subbed)
return subbed
try:
connection = sqlite3.connect(args.output)
cursor = connection.cursor()
@@ -28,23 +40,41 @@ try:
cursor.execute("PRAGMA page_size=32768")
cursor.execute("VACUUM")
cursor.execute("DROP TABLE IF EXISTS apns")
cursor.execute("""CREATE TABLE apns(_id INTEGER PRIMARY KEY, mccmnc TEXT, mcc TEXT, mnc TEXT, carrier TEXT, apn TEXT,
mmsc TEXT, port INTEGER, type TEXT, protocol TEXT, bearer TEXT, roaming_protocol TEXT,
cursor.execute("""CREATE TABLE apns(_id INTEGER PRIMARY KEY, mccmnc TEXT, mcc TEXT, mnc TEXT, carrier TEXT,
apn TEXT, mmsc TEXT, port INTEGER, type TEXT, protocol TEXT, bearer TEXT, roaming_protocol TEXT,
carrier_enabled INTEGER, mmsproxy TEXT, mmsport INTEGER, proxy TEXT, mvno_match_data TEXT,
mvno_type TEXT, authtype INTEGER, user TEXT, password TEXT, server TEXT)""")
apns = etree.parse(args.input)
root = apns.getroot()
pbar = ProgressBar(widgets=['Processed: ', Counter(), ' apns (', Timer(), ')'], maxval=len(list(root))).start() if not args.quiet else None
pbar = None
if not args.quiet:
pbar = ProgressBar(widgets=['Processed: ', Counter(), ' apns (', Timer(), ')'], maxval=len(list(root))).start()
count = 0
for apn in root.iter("apn"):
if apn.get("mmsc") is None:
continue
sqlvars = ["?" for x in apn.attrib.keys()] + ["?"]
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + ["%s%s" % (apn.get("mcc"), apn.get("mnc"))]
mccmnc = "%s%s" % (apn.get("mcc"), apn.get("mnc"))
normalized_mmsc = normalized(apn.get("mmsc"))
if normalized_mmsc != apn.get("mmsc"):
print("normalize MMSC: %s => %s" % (apn.get("mmsc"), normalized_mmsc))
apn.set("mmsc", normalized_mmsc)
if not apn.get("mmsproxy") is None:
normalized_mmsproxy = normalized(apn.get("mmsproxy"))
if normalized_mmsproxy != apn.get("mmsproxy"):
print("normalize proxy: %s => %s" % (apn.get("mmsproxy"), normalized_mmsproxy))
apn.set("mmsproxy", normalized_mmsproxy)
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + [mccmnc]
keys = apn.attrib.keys() + ["mccmnc"]
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
cursor.execute(statement, values)
cursor.execute("SELECT 1 FROM apns WHERE mccmnc = ? AND apn = ?", [mccmnc, apn.get("apn")])
if cursor.fetchone() is None:
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
cursor.execute(statement, values)
count += 1
if not args.quiet:

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

BIN
artwork/logo-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

View File

@@ -1,9 +1,11 @@
buildscript {
repositories {
mavenCentral()
maven {
url "https://repo1.maven.org/maven2"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.2'
classpath 'com.android.tools.build:gradle:1.0.1'
classpath files('libs/gradle-witness.jar')
}
}
@@ -12,89 +14,192 @@ apply plugin: 'com.android.application'
apply plugin: 'witness'
repositories {
mavenCentral()
maven {
url "https://raw.github.com/whispersystems/maven/master/gcm-client/releases/"
url "https://repo1.maven.org/maven2/"
}
maven {
url "https://raw.github.com/whispersystems/maven/master/gson/releases/"
url "https://raw.github.com/whispersystems/maven/master/preferencefragment/releases/"
}
maven {
url "https://raw.github.com/whispersystems/maven/master/smil/releases/"
}
maven {
url "https://raw.github.com/whispersystems/maven/master/shortcutbadger/releases/"
}
jcenter()
mavenLocal()
}
dependencies {
compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
compile 'com.android.support:support-v4:20.0.0'
compile 'me.leolin:ShortcutBadger:1.0.2-WS2'
compile 'se.emilsjolander:stickylistheaders:2.2.0'
compile 'com.google.android.gms:play-services:5.0.89'
compile 'com.google.android.gms:play-services-base:6.5.87'
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'org.w3c:smil:1.0.0'
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
compile 'com.makeramen:roundedimageview:1.5.0'
compile ('com.afollestad:material-dialogs:0.6.4.7') {
exclude module: 'appcompat-v7'
exclude module: 'recyclerview-v7'
exclude module: 'support-annotations'
}
androidTestCompile 'com.squareup:fest-android:1.0.8'
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'com.melnykov:floatingactionbutton:1.1.0'
compile 'com.google.zxing:android-integration:3.1.0'
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4'
}
compile 'com.squareup.dagger:dagger:1.2.2'
compile ("com.doomonafireball.betterpickers:library:1.5.3") {
exclude group: 'com.android.support', module: 'support-v4'
}
compile 'com.madgag.spongycastle:prov:1.51.0.0'
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
compile project(':library')
compile 'org.whispersystems:jobmanager:0.11.0'
compile 'org.whispersystems:libpastelog:1.0.6'
compile 'org.whispersystems:textsecure-android:1.2.4'
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile ('org.assertj:assertj-core:1.7.1') {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
androidTestCompile ('com.squareup.assertj:assertj-android:1.0.0') {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
androidTestCompile ('com.android.support.test.espresso:espresso-core:2.0') {
exclude group: 'javax.inject'
}
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
}
dependencyVerification {
verify = [
'com.actionbarsherlock:actionbarsherlock:5ab04d74101f70024b222e3ff9c87bee151ec43331b4a2134b6cc08cf8565819',
'com.android.support:support-v4:81f2b1c2c94efd5a4ec7fcd97b6cdcd00e87a933905c5c86103c7319eb024572',
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
'com.google.android.gms:play-services:38f326e525830f1d70f60f594ceafcbdf5b312287ddbecd338fd1ed7958a4b1e',
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
'com.google.protobuf:protobuf-java:ad9769a22989e688a46af4d3accc348cc501ced22118033230542bc916e33f0b',
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
'me.leolin:ShortcutBadger:027977c718035e5997035e04e05152d6c72d94df645e8b7099a274ada722bd14',
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
'com.google.android.gms:play-services-base:832cb6b3130e871db6a412c4ab585656dbcc5e7948101f190186757785703f75',
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
'com.makeramen:roundedimageview:7dda2e78c406760e5c356ccce59b0df46b5b171cf18abb891998594405021548',
'com.afollestad:material-dialogs:6ed57c1c479219f8ae929c310fc171dbfcbcee8326a6dcd50a91959d69eccdf0',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d',
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
'org.whispersystems:textsecure-android:c867b4e1f9cf430aa15c73841833469f71af9c6149becea447a72b96de214398',
'com.android.support:support-annotations:fdee2354787ef66b268e75958de3f7f6c4f8f325510a6dac9f49c929f83a63de',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
'org.whispersystems:textsecure-java:141132e1c917759bdc285f80ed9e9784be0dc99ec6872f738439505955832906',
'org.whispersystems:axolotl-android:40d3db5004a84749a73f68d2f0d01b2ae35a73c54df96d8c6c6723b96efb6fc0',
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
'com.squareup.okhttp:okhttp:89b7f63e2e5b6c410266abc14f50fe52ea8d2d8a57260829e499b1cd9f0e61af',
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
'org.whispersystems:axolotl-java:6daee739b89d8d7101de6d98f77132fee48495c6ea647d880e77def842f999ea',
'org.whispersystems:curve25519-android:3c29a4131a69b0d16baaa3d707678deb39602c3a3ffd75805ce7f9db252e5d0d',
'com.squareup.okio:okio:5e1098bd3fdee4c3347f5ab815b40ba851e4ab1b348c5e49a5b0362f0ce6e978',
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
'org.whispersystems:curve25519-java:9ccef8f5aba05d9942336f023c589d6278b4f9135bdc34a7bade1f4e7ad65fa3',
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
]
}
android {
compileSdkVersion 19
buildToolsVersion '19.1.0'
compileSdkVersion 22
buildToolsVersion '22.0.1'
dexOptions {
javaMaxHeapSize "4g"
}
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L"
}
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest {
java.srcDirs = ['androidTest']
resources.srcDirs = ['androidTest']
aidl.srcDirs = ['androidTest']
renderscript.srcDirs = ['androidTest']
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
packagingOptions {
exclude 'LICENSE.txt'
exclude 'LICENSE'
exclude 'NOTICE'
exclude 'asm-license.txt'
}
signingConfigs {
release
}
buildTypes {
debug {
minifyEnabled true
proguardFiles 'proguard.cfg'
}
release {
minifyEnabled true
proguardFiles 'proguard.cfg'
signingConfig signingConfigs.release
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest {
java.srcDirs = ['androidTest/java']
resources.srcDirs = ['androidTest/java']
aidl.srcDirs = ['androidTest/java']
renderscript.srcDirs = ['androidTest/java']
}
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
}
lintOptions {
abortOnError false
}
}
tasks.whenTaskAdded { task ->
if (task.name.equals("lint")) {
task.enabled = false
}
}
def Properties props = new Properties()
def propFile = new File('signing.properties')

View File

@@ -1,6 +1,6 @@
#Mon Jun 09 23:26:49 PDT 2014
#Fri Nov 28 10:03:17 PST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.whispersystems.textsecure"
android:versionCode="1"
android:versionName="0.1">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="16"/>
<application />
</manifest>

View File

@@ -1,58 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.2'
}
}
apply plugin: 'android-library'
apply plugin: 'maven'
repositories {
mavenCentral()
maven {
url "https://raw.github.com/whispersystems/maven/master/gson/releases/"
}
}
dependencies {
compile 'com.google.protobuf:protobuf-java:2.4.1'
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
compile 'org.whispersystems:gson:2.2.4'
}
android {
compileSdkVersion 19
buildToolsVersion '19.1.0'
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
}
}
version '0.1'
group 'org.whispersystems.textsecure'
archivesBaseName = 'textsecure-library'
uploadArchives {
repositories {
mavenDeployer {
repository(url: mavenLocal().getUrl())
}
}
}

View File

@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="library" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties"/>
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties"/>
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env"/>
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME"/>
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties"/>
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true"/>
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml"/>
</project>

View File

@@ -1,17 +0,0 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libcurve25519-donna
LOCAL_SRC_FILES := curve25519-donna.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libcurve25519
LOCAL_SRC_FILES := curve25519-donna-jni.c
LOCAL_STATIC_LIBRARIES := libcurve25519-donna
include $(BUILD_SHARED_LIBRARY)

View File

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

View File

@@ -1,75 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdint.h>
#include <jni.h>
#include "curve25519-donna.h"
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePrivateKey
(JNIEnv *env, jclass clazz, jbyteArray random, jboolean ephemeral)
{
uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
privateKey[0] &= 248;
if (ephemeral) {
privateKey[0] |= 1;
}
privateKey[31] &= 127;
privateKey[31] |= 64;
(*env)->ReleaseByteArrayElements(env, random, privateKey, 0);
return random;
}
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePublicKey
(JNIEnv *env, jclass clazz, jbyteArray privateKey)
{
static const uint8_t basepoint[32] = {9};
jbyteArray publicKey = (*env)->NewByteArray(env, 32);
uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0);
uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0);
curve25519_donna(publicKeyBytes, privateKeyBytes, basepoint);
(*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0);
(*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0);
return publicKey;
}
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_calculateAgreement
(JNIEnv *env, jclass clazz, jbyteArray privateKey, jbyteArray publicKey)
{
jbyteArray sharedKey = (*env)->NewByteArray(env, 32);
uint8_t* sharedKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, sharedKey, 0);
uint8_t* privateKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, privateKey, 0);
uint8_t* publicKeyBytes = (uint8_t*)(*env)->GetByteArrayElements(env, publicKey, 0);
curve25519_donna(sharedKeyBytes, privateKeyBytes, publicKeyBytes);
(*env)->ReleaseByteArrayElements(env, sharedKey, sharedKeyBytes, 0);
(*env)->ReleaseByteArrayElements(env, publicKey, publicKeyBytes, 0);
(*env)->ReleaseByteArrayElements(env, privateKey, privateKeyBytes, 0);
return sharedKey;
}

View File

@@ -1,731 +0,0 @@
/* Copyright 2008, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* curve25519-donna: Curve25519 elliptic curve, public key function
*
* http://code.google.com/p/curve25519-donna/
*
* Adam Langley <agl@imperialviolet.org>
*
* Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
*
* More information about curve25519 can be found here
* http://cr.yp.to/ecdh.html
*
* djb's sample implementation of curve25519 is written in a special assembly
* language called qhasm and uses the floating point registers.
*
* This is, almost, a clean room reimplementation from the curve25519 paper. It
* uses many of the tricks described therein. Only the crecip function is taken
* from the sample implementation.
*/
#include <string.h>
#include <stdint.h>
#ifdef _MSC_VER
#define inline __inline
#endif
typedef uint8_t u8;
typedef int32_t s32;
typedef int64_t limb;
/* Field element representation:
*
* Field elements are written as an array of signed, 64-bit limbs, least
* significant first. The value of the field element is:
* x[0] + 2^26·x[1] + x^51·x[2] + 2^102·x[3] + ...
*
* i.e. the limbs are 26, 25, 26, 25, ... bits wide.
*/
/* Sum two numbers: output += in */
static void fsum(limb *output, const limb *in) {
unsigned i;
for (i = 0; i < 10; i += 2) {
output[0+i] = (output[0+i] + in[0+i]);
output[1+i] = (output[1+i] + in[1+i]);
}
}
/* Find the difference of two numbers: output = in - output
* (note the order of the arguments!)
*/
static void fdifference(limb *output, const limb *in) {
unsigned i;
for (i = 0; i < 10; ++i) {
output[i] = (in[i] - output[i]);
}
}
/* Multiply a number by a scalar: output = in * scalar */
static void fscalar_product(limb *output, const limb *in, const limb scalar) {
unsigned i;
for (i = 0; i < 10; ++i) {
output[i] = in[i] * scalar;
}
}
/* Multiply two numbers: output = in2 * in
*
* output must be distinct to both inputs. The inputs are reduced coefficient
* form, the output is not.
*/
static void fproduct(limb *output, const limb *in2, const limb *in) {
output[0] = ((limb) ((s32) in2[0])) * ((s32) in[0]);
output[1] = ((limb) ((s32) in2[0])) * ((s32) in[1]) +
((limb) ((s32) in2[1])) * ((s32) in[0]);
output[2] = 2 * ((limb) ((s32) in2[1])) * ((s32) in[1]) +
((limb) ((s32) in2[0])) * ((s32) in[2]) +
((limb) ((s32) in2[2])) * ((s32) in[0]);
output[3] = ((limb) ((s32) in2[1])) * ((s32) in[2]) +
((limb) ((s32) in2[2])) * ((s32) in[1]) +
((limb) ((s32) in2[0])) * ((s32) in[3]) +
((limb) ((s32) in2[3])) * ((s32) in[0]);
output[4] = ((limb) ((s32) in2[2])) * ((s32) in[2]) +
2 * (((limb) ((s32) in2[1])) * ((s32) in[3]) +
((limb) ((s32) in2[3])) * ((s32) in[1])) +
((limb) ((s32) in2[0])) * ((s32) in[4]) +
((limb) ((s32) in2[4])) * ((s32) in[0]);
output[5] = ((limb) ((s32) in2[2])) * ((s32) in[3]) +
((limb) ((s32) in2[3])) * ((s32) in[2]) +
((limb) ((s32) in2[1])) * ((s32) in[4]) +
((limb) ((s32) in2[4])) * ((s32) in[1]) +
((limb) ((s32) in2[0])) * ((s32) in[5]) +
((limb) ((s32) in2[5])) * ((s32) in[0]);
output[6] = 2 * (((limb) ((s32) in2[3])) * ((s32) in[3]) +
((limb) ((s32) in2[1])) * ((s32) in[5]) +
((limb) ((s32) in2[5])) * ((s32) in[1])) +
((limb) ((s32) in2[2])) * ((s32) in[4]) +
((limb) ((s32) in2[4])) * ((s32) in[2]) +
((limb) ((s32) in2[0])) * ((s32) in[6]) +
((limb) ((s32) in2[6])) * ((s32) in[0]);
output[7] = ((limb) ((s32) in2[3])) * ((s32) in[4]) +
((limb) ((s32) in2[4])) * ((s32) in[3]) +
((limb) ((s32) in2[2])) * ((s32) in[5]) +
((limb) ((s32) in2[5])) * ((s32) in[2]) +
((limb) ((s32) in2[1])) * ((s32) in[6]) +
((limb) ((s32) in2[6])) * ((s32) in[1]) +
((limb) ((s32) in2[0])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[0]);
output[8] = ((limb) ((s32) in2[4])) * ((s32) in[4]) +
2 * (((limb) ((s32) in2[3])) * ((s32) in[5]) +
((limb) ((s32) in2[5])) * ((s32) in[3]) +
((limb) ((s32) in2[1])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[1])) +
((limb) ((s32) in2[2])) * ((s32) in[6]) +
((limb) ((s32) in2[6])) * ((s32) in[2]) +
((limb) ((s32) in2[0])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[0]);
output[9] = ((limb) ((s32) in2[4])) * ((s32) in[5]) +
((limb) ((s32) in2[5])) * ((s32) in[4]) +
((limb) ((s32) in2[3])) * ((s32) in[6]) +
((limb) ((s32) in2[6])) * ((s32) in[3]) +
((limb) ((s32) in2[2])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[2]) +
((limb) ((s32) in2[1])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[1]) +
((limb) ((s32) in2[0])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[0]);
output[10] = 2 * (((limb) ((s32) in2[5])) * ((s32) in[5]) +
((limb) ((s32) in2[3])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[3]) +
((limb) ((s32) in2[1])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[1])) +
((limb) ((s32) in2[4])) * ((s32) in[6]) +
((limb) ((s32) in2[6])) * ((s32) in[4]) +
((limb) ((s32) in2[2])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[2]);
output[11] = ((limb) ((s32) in2[5])) * ((s32) in[6]) +
((limb) ((s32) in2[6])) * ((s32) in[5]) +
((limb) ((s32) in2[4])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[4]) +
((limb) ((s32) in2[3])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[3]) +
((limb) ((s32) in2[2])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[2]);
output[12] = ((limb) ((s32) in2[6])) * ((s32) in[6]) +
2 * (((limb) ((s32) in2[5])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[5]) +
((limb) ((s32) in2[3])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[3])) +
((limb) ((s32) in2[4])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[4]);
output[13] = ((limb) ((s32) in2[6])) * ((s32) in[7]) +
((limb) ((s32) in2[7])) * ((s32) in[6]) +
((limb) ((s32) in2[5])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[5]) +
((limb) ((s32) in2[4])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[4]);
output[14] = 2 * (((limb) ((s32) in2[7])) * ((s32) in[7]) +
((limb) ((s32) in2[5])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[5])) +
((limb) ((s32) in2[6])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[6]);
output[15] = ((limb) ((s32) in2[7])) * ((s32) in[8]) +
((limb) ((s32) in2[8])) * ((s32) in[7]) +
((limb) ((s32) in2[6])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[6]);
output[16] = ((limb) ((s32) in2[8])) * ((s32) in[8]) +
2 * (((limb) ((s32) in2[7])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[7]));
output[17] = ((limb) ((s32) in2[8])) * ((s32) in[9]) +
((limb) ((s32) in2[9])) * ((s32) in[8]);
output[18] = 2 * ((limb) ((s32) in2[9])) * ((s32) in[9]);
}
/* Reduce a long form to a short form by taking the input mod 2^255 - 19. */
static void freduce_degree(limb *output) {
/* Each of these shifts and adds ends up multiplying the value by 19. */
output[8] += output[18] << 4;
output[8] += output[18] << 1;
output[8] += output[18];
output[7] += output[17] << 4;
output[7] += output[17] << 1;
output[7] += output[17];
output[6] += output[16] << 4;
output[6] += output[16] << 1;
output[6] += output[16];
output[5] += output[15] << 4;
output[5] += output[15] << 1;
output[5] += output[15];
output[4] += output[14] << 4;
output[4] += output[14] << 1;
output[4] += output[14];
output[3] += output[13] << 4;
output[3] += output[13] << 1;
output[3] += output[13];
output[2] += output[12] << 4;
output[2] += output[12] << 1;
output[2] += output[12];
output[1] += output[11] << 4;
output[1] += output[11] << 1;
output[1] += output[11];
output[0] += output[10] << 4;
output[0] += output[10] << 1;
output[0] += output[10];
}
#if (-1 & 3) != 3
#error "This code only works on a two's complement system"
#endif
/* return v / 2^26, using only shifts and adds. */
static inline limb
div_by_2_26(const limb v)
{
/* High word of v; no shift needed*/
const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32);
/* Set to all 1s if v was negative; else set to 0s. */
const int32_t sign = ((int32_t) highword) >> 31;
/* Set to 0x3ffffff if v was negative; else set to 0. */
const int32_t roundoff = ((uint32_t) sign) >> 6;
/* Should return v / (1<<26) */
return (v + roundoff) >> 26;
}
/* return v / (2^25), using only shifts and adds. */
static inline limb
div_by_2_25(const limb v)
{
/* High word of v; no shift needed*/
const uint32_t highword = (uint32_t) (((uint64_t) v) >> 32);
/* Set to all 1s if v was negative; else set to 0s. */
const int32_t sign = ((int32_t) highword) >> 31;
/* Set to 0x1ffffff if v was negative; else set to 0. */
const int32_t roundoff = ((uint32_t) sign) >> 7;
/* Should return v / (1<<25) */
return (v + roundoff) >> 25;
}
static inline s32
div_s32_by_2_25(const s32 v)
{
const s32 roundoff = ((uint32_t)(v >> 31)) >> 7;
return (v + roundoff) >> 25;
}
/* Reduce all coefficients of the short form input so that |x| < 2^26.
*
* On entry: |output[i]| < 2^62
*/
static void freduce_coefficients(limb *output) {
unsigned i;
output[10] = 0;
for (i = 0; i < 10; i += 2) {
limb over = div_by_2_26(output[i]);
output[i] -= over << 26;
output[i+1] += over;
over = div_by_2_25(output[i+1]);
output[i+1] -= over << 25;
output[i+2] += over;
}
/* Now |output[10]| < 2 ^ 38 and all other coefficients are reduced. */
output[0] += output[10] << 4;
output[0] += output[10] << 1;
output[0] += output[10];
output[10] = 0;
/* Now output[1..9] are reduced, and |output[0]| < 2^26 + 19 * 2^38
* So |over| will be no more than 77825 */
{
limb over = div_by_2_26(output[0]);
output[0] -= over << 26;
output[1] += over;
}
/* Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 77825
* So |over| will be no more than 1. */
{
/* output[1] fits in 32 bits, so we can use div_s32_by_2_25 here. */
s32 over32 = div_s32_by_2_25((s32) output[1]);
output[1] -= over32 << 25;
output[2] += over32;
}
/* Finally, output[0,1,3..9] are reduced, and output[2] is "nearly reduced":
* we have |output[2]| <= 2^26. This is good enough for all of our math,
* but it will require an extra freduce_coefficients before fcontract. */
}
/* A helpful wrapper around fproduct: output = in * in2.
*
* output must be distinct to both inputs. The output is reduced degree and
* reduced coefficient.
*/
static void
fmul(limb *output, const limb *in, const limb *in2) {
limb t[19];
fproduct(t, in, in2);
freduce_degree(t);
freduce_coefficients(t);
memcpy(output, t, sizeof(limb) * 10);
}
static void fsquare_inner(limb *output, const limb *in) {
output[0] = ((limb) ((s32) in[0])) * ((s32) in[0]);
output[1] = 2 * ((limb) ((s32) in[0])) * ((s32) in[1]);
output[2] = 2 * (((limb) ((s32) in[1])) * ((s32) in[1]) +
((limb) ((s32) in[0])) * ((s32) in[2]));
output[3] = 2 * (((limb) ((s32) in[1])) * ((s32) in[2]) +
((limb) ((s32) in[0])) * ((s32) in[3]));
output[4] = ((limb) ((s32) in[2])) * ((s32) in[2]) +
4 * ((limb) ((s32) in[1])) * ((s32) in[3]) +
2 * ((limb) ((s32) in[0])) * ((s32) in[4]);
output[5] = 2 * (((limb) ((s32) in[2])) * ((s32) in[3]) +
((limb) ((s32) in[1])) * ((s32) in[4]) +
((limb) ((s32) in[0])) * ((s32) in[5]));
output[6] = 2 * (((limb) ((s32) in[3])) * ((s32) in[3]) +
((limb) ((s32) in[2])) * ((s32) in[4]) +
((limb) ((s32) in[0])) * ((s32) in[6]) +
2 * ((limb) ((s32) in[1])) * ((s32) in[5]));
output[7] = 2 * (((limb) ((s32) in[3])) * ((s32) in[4]) +
((limb) ((s32) in[2])) * ((s32) in[5]) +
((limb) ((s32) in[1])) * ((s32) in[6]) +
((limb) ((s32) in[0])) * ((s32) in[7]));
output[8] = ((limb) ((s32) in[4])) * ((s32) in[4]) +
2 * (((limb) ((s32) in[2])) * ((s32) in[6]) +
((limb) ((s32) in[0])) * ((s32) in[8]) +
2 * (((limb) ((s32) in[1])) * ((s32) in[7]) +
((limb) ((s32) in[3])) * ((s32) in[5])));
output[9] = 2 * (((limb) ((s32) in[4])) * ((s32) in[5]) +
((limb) ((s32) in[3])) * ((s32) in[6]) +
((limb) ((s32) in[2])) * ((s32) in[7]) +
((limb) ((s32) in[1])) * ((s32) in[8]) +
((limb) ((s32) in[0])) * ((s32) in[9]));
output[10] = 2 * (((limb) ((s32) in[5])) * ((s32) in[5]) +
((limb) ((s32) in[4])) * ((s32) in[6]) +
((limb) ((s32) in[2])) * ((s32) in[8]) +
2 * (((limb) ((s32) in[3])) * ((s32) in[7]) +
((limb) ((s32) in[1])) * ((s32) in[9])));
output[11] = 2 * (((limb) ((s32) in[5])) * ((s32) in[6]) +
((limb) ((s32) in[4])) * ((s32) in[7]) +
((limb) ((s32) in[3])) * ((s32) in[8]) +
((limb) ((s32) in[2])) * ((s32) in[9]));
output[12] = ((limb) ((s32) in[6])) * ((s32) in[6]) +
2 * (((limb) ((s32) in[4])) * ((s32) in[8]) +
2 * (((limb) ((s32) in[5])) * ((s32) in[7]) +
((limb) ((s32) in[3])) * ((s32) in[9])));
output[13] = 2 * (((limb) ((s32) in[6])) * ((s32) in[7]) +
((limb) ((s32) in[5])) * ((s32) in[8]) +
((limb) ((s32) in[4])) * ((s32) in[9]));
output[14] = 2 * (((limb) ((s32) in[7])) * ((s32) in[7]) +
((limb) ((s32) in[6])) * ((s32) in[8]) +
2 * ((limb) ((s32) in[5])) * ((s32) in[9]));
output[15] = 2 * (((limb) ((s32) in[7])) * ((s32) in[8]) +
((limb) ((s32) in[6])) * ((s32) in[9]));
output[16] = ((limb) ((s32) in[8])) * ((s32) in[8]) +
4 * ((limb) ((s32) in[7])) * ((s32) in[9]);
output[17] = 2 * ((limb) ((s32) in[8])) * ((s32) in[9]);
output[18] = 2 * ((limb) ((s32) in[9])) * ((s32) in[9]);
}
static void
fsquare(limb *output, const limb *in) {
limb t[19];
fsquare_inner(t, in);
freduce_degree(t);
freduce_coefficients(t);
memcpy(output, t, sizeof(limb) * 10);
}
/* Take a little-endian, 32-byte number and expand it into polynomial form */
static void
fexpand(limb *output, const u8 *input) {
#define F(n,start,shift,mask) \
output[n] = ((((limb) input[start + 0]) | \
((limb) input[start + 1]) << 8 | \
((limb) input[start + 2]) << 16 | \
((limb) input[start + 3]) << 24) >> shift) & mask;
F(0, 0, 0, 0x3ffffff);
F(1, 3, 2, 0x1ffffff);
F(2, 6, 3, 0x3ffffff);
F(3, 9, 5, 0x1ffffff);
F(4, 12, 6, 0x3ffffff);
F(5, 16, 0, 0x1ffffff);
F(6, 19, 1, 0x3ffffff);
F(7, 22, 3, 0x1ffffff);
F(8, 25, 4, 0x3ffffff);
F(9, 28, 6, 0x3ffffff);
#undef F
}
#if (-32 >> 1) != -16
#error "This code only works when >> does sign-extension on negative numbers"
#endif
/* Take a fully reduced polynomial form number and contract it into a
* little-endian, 32-byte array
*/
static void
fcontract(u8 *output, limb *input) {
int i;
int j;
for (j = 0; j < 2; ++j) {
for (i = 0; i < 9; ++i) {
if ((i & 1) == 1) {
/* This calculation is a time-invariant way to make input[i] positive
by borrowing from the next-larger limb.
*/
const s32 mask = (s32)(input[i]) >> 31;
const s32 carry = -(((s32)(input[i]) & mask) >> 25);
input[i] = (s32)(input[i]) + (carry << 25);
input[i+1] = (s32)(input[i+1]) - carry;
} else {
const s32 mask = (s32)(input[i]) >> 31;
const s32 carry = -(((s32)(input[i]) & mask) >> 26);
input[i] = (s32)(input[i]) + (carry << 26);
input[i+1] = (s32)(input[i+1]) - carry;
}
}
{
const s32 mask = (s32)(input[9]) >> 31;
const s32 carry = -(((s32)(input[9]) & mask) >> 25);
input[9] = (s32)(input[9]) + (carry << 25);
input[0] = (s32)(input[0]) - (carry * 19);
}
}
/* The first borrow-propagation pass above ended with every limb
except (possibly) input[0] non-negative.
Since each input limb except input[0] is decreased by at most 1
by a borrow-propagation pass, the second borrow-propagation pass
could only have wrapped around to decrease input[0] again if the
first pass left input[0] negative *and* input[1] through input[9]
were all zero. In that case, input[1] is now 2^25 - 1, and this
last borrow-propagation step will leave input[1] non-negative.
*/
{
const s32 mask = (s32)(input[0]) >> 31;
const s32 carry = -(((s32)(input[0]) & mask) >> 26);
input[0] = (s32)(input[0]) + (carry << 26);
input[1] = (s32)(input[1]) - carry;
}
/* Both passes through the above loop, plus the last 0-to-1 step, are
necessary: if input[9] is -1 and input[0] through input[8] are 0,
negative values will remain in the array until the end.
*/
input[1] <<= 2;
input[2] <<= 3;
input[3] <<= 5;
input[4] <<= 6;
input[6] <<= 1;
input[7] <<= 3;
input[8] <<= 4;
input[9] <<= 6;
#define F(i, s) \
output[s+0] |= input[i] & 0xff; \
output[s+1] = (input[i] >> 8) & 0xff; \
output[s+2] = (input[i] >> 16) & 0xff; \
output[s+3] = (input[i] >> 24) & 0xff;
output[0] = 0;
output[16] = 0;
F(0,0);
F(1,3);
F(2,6);
F(3,9);
F(4,12);
F(5,16);
F(6,19);
F(7,22);
F(8,25);
F(9,28);
#undef F
}
/* Input: Q, Q', Q-Q'
* Output: 2Q, Q+Q'
*
* x2 z3: long form
* x3 z3: long form
* x z: short form, destroyed
* xprime zprime: short form, destroyed
* qmqp: short form, preserved
*/
static void fmonty(limb *x2, limb *z2, /* output 2Q */
limb *x3, limb *z3, /* output Q + Q' */
limb *x, limb *z, /* input Q */
limb *xprime, limb *zprime, /* input Q' */
const limb *qmqp /* input Q - Q' */) {
limb origx[10], origxprime[10], zzz[19], xx[19], zz[19], xxprime[19],
zzprime[19], zzzprime[19], xxxprime[19];
memcpy(origx, x, 10 * sizeof(limb));
fsum(x, z);
fdifference(z, origx); // does x - z
memcpy(origxprime, xprime, sizeof(limb) * 10);
fsum(xprime, zprime);
fdifference(zprime, origxprime);
fproduct(xxprime, xprime, z);
fproduct(zzprime, x, zprime);
freduce_degree(xxprime);
freduce_coefficients(xxprime);
freduce_degree(zzprime);
freduce_coefficients(zzprime);
memcpy(origxprime, xxprime, sizeof(limb) * 10);
fsum(xxprime, zzprime);
fdifference(zzprime, origxprime);
fsquare(xxxprime, xxprime);
fsquare(zzzprime, zzprime);
fproduct(zzprime, zzzprime, qmqp);
freduce_degree(zzprime);
freduce_coefficients(zzprime);
memcpy(x3, xxxprime, sizeof(limb) * 10);
memcpy(z3, zzprime, sizeof(limb) * 10);
fsquare(xx, x);
fsquare(zz, z);
fproduct(x2, xx, zz);
freduce_degree(x2);
freduce_coefficients(x2);
fdifference(zz, xx); // does zz = xx - zz
memset(zzz + 10, 0, sizeof(limb) * 9);
fscalar_product(zzz, zz, 121665);
/* No need to call freduce_degree here:
fscalar_product doesn't increase the degree of its input. */
freduce_coefficients(zzz);
fsum(zzz, xx);
fproduct(z2, zz, zzz);
freduce_degree(z2);
freduce_coefficients(z2);
}
/* Conditionally swap two reduced-form limb arrays if 'iswap' is 1, but leave
* them unchanged if 'iswap' is 0. Runs in data-invariant time to avoid
* side-channel attacks.
*
* NOTE that this function requires that 'iswap' be 1 or 0; other values give
* wrong results. Also, the two limb arrays must be in reduced-coefficient,
* reduced-degree form: the values in a[10..19] or b[10..19] aren't swapped,
* and all all values in a[0..9],b[0..9] must have magnitude less than
* INT32_MAX.
*/
static void
swap_conditional(limb a[19], limb b[19], limb iswap) {
unsigned i;
const s32 swap = (s32) -iswap;
for (i = 0; i < 10; ++i) {
const s32 x = swap & ( ((s32)a[i]) ^ ((s32)b[i]) );
a[i] = ((s32)a[i]) ^ x;
b[i] = ((s32)b[i]) ^ x;
}
}
/* Calculates nQ where Q is the x-coordinate of a point on the curve
*
* resultx/resultz: the x coordinate of the resulting curve point (short form)
* n: a little endian, 32-byte number
* q: a point of the curve (short form)
*/
static void
cmult(limb *resultx, limb *resultz, const u8 *n, const limb *q) {
limb a[19] = {0}, b[19] = {1}, c[19] = {1}, d[19] = {0};
limb *nqpqx = a, *nqpqz = b, *nqx = c, *nqz = d, *t;
limb e[19] = {0}, f[19] = {1}, g[19] = {0}, h[19] = {1};
limb *nqpqx2 = e, *nqpqz2 = f, *nqx2 = g, *nqz2 = h;
unsigned i, j;
memcpy(nqpqx, q, sizeof(limb) * 10);
for (i = 0; i < 32; ++i) {
u8 byte = n[31 - i];
for (j = 0; j < 8; ++j) {
const limb bit = byte >> 7;
swap_conditional(nqx, nqpqx, bit);
swap_conditional(nqz, nqpqz, bit);
fmonty(nqx2, nqz2,
nqpqx2, nqpqz2,
nqx, nqz,
nqpqx, nqpqz,
q);
swap_conditional(nqx2, nqpqx2, bit);
swap_conditional(nqz2, nqpqz2, bit);
t = nqx;
nqx = nqx2;
nqx2 = t;
t = nqz;
nqz = nqz2;
nqz2 = t;
t = nqpqx;
nqpqx = nqpqx2;
nqpqx2 = t;
t = nqpqz;
nqpqz = nqpqz2;
nqpqz2 = t;
byte <<= 1;
}
}
memcpy(resultx, nqx, sizeof(limb) * 10);
memcpy(resultz, nqz, sizeof(limb) * 10);
}
// -----------------------------------------------------------------------------
// Shamelessly copied from djb's code
// -----------------------------------------------------------------------------
static void
crecip(limb *out, const limb *z) {
limb z2[10];
limb z9[10];
limb z11[10];
limb z2_5_0[10];
limb z2_10_0[10];
limb z2_20_0[10];
limb z2_50_0[10];
limb z2_100_0[10];
limb t0[10];
limb t1[10];
int i;
/* 2 */ fsquare(z2,z);
/* 4 */ fsquare(t1,z2);
/* 8 */ fsquare(t0,t1);
/* 9 */ fmul(z9,t0,z);
/* 11 */ fmul(z11,z9,z2);
/* 22 */ fsquare(t0,z11);
/* 2^5 - 2^0 = 31 */ fmul(z2_5_0,t0,z9);
/* 2^6 - 2^1 */ fsquare(t0,z2_5_0);
/* 2^7 - 2^2 */ fsquare(t1,t0);
/* 2^8 - 2^3 */ fsquare(t0,t1);
/* 2^9 - 2^4 */ fsquare(t1,t0);
/* 2^10 - 2^5 */ fsquare(t0,t1);
/* 2^10 - 2^0 */ fmul(z2_10_0,t0,z2_5_0);
/* 2^11 - 2^1 */ fsquare(t0,z2_10_0);
/* 2^12 - 2^2 */ fsquare(t1,t0);
/* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
/* 2^20 - 2^0 */ fmul(z2_20_0,t1,z2_10_0);
/* 2^21 - 2^1 */ fsquare(t0,z2_20_0);
/* 2^22 - 2^2 */ fsquare(t1,t0);
/* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
/* 2^40 - 2^0 */ fmul(t0,t1,z2_20_0);
/* 2^41 - 2^1 */ fsquare(t1,t0);
/* 2^42 - 2^2 */ fsquare(t0,t1);
/* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
/* 2^50 - 2^0 */ fmul(z2_50_0,t0,z2_10_0);
/* 2^51 - 2^1 */ fsquare(t0,z2_50_0);
/* 2^52 - 2^2 */ fsquare(t1,t0);
/* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
/* 2^100 - 2^0 */ fmul(z2_100_0,t1,z2_50_0);
/* 2^101 - 2^1 */ fsquare(t1,z2_100_0);
/* 2^102 - 2^2 */ fsquare(t0,t1);
/* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fsquare(t1,t0); fsquare(t0,t1); }
/* 2^200 - 2^0 */ fmul(t1,t0,z2_100_0);
/* 2^201 - 2^1 */ fsquare(t0,t1);
/* 2^202 - 2^2 */ fsquare(t1,t0);
/* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fsquare(t0,t1); fsquare(t1,t0); }
/* 2^250 - 2^0 */ fmul(t0,t1,z2_50_0);
/* 2^251 - 2^1 */ fsquare(t1,t0);
/* 2^252 - 2^2 */ fsquare(t0,t1);
/* 2^253 - 2^3 */ fsquare(t1,t0);
/* 2^254 - 2^4 */ fsquare(t0,t1);
/* 2^255 - 2^5 */ fsquare(t1,t0);
/* 2^255 - 21 */ fmul(out,t1,z11);
}
int curve25519_donna(u8 *, const u8 *, const u8 *);
int
curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
limb bp[10], x[10], z[11], zmone[10];
uint8_t e[32];
int i;
for (i = 0; i < 32; ++i) e[i] = secret[i];
fexpand(bp, basepoint);
cmult(x, z, e, bp);
crecip(zmone, z);
fmul(z, x, zmone);
freduce_coefficients(z);
fcontract(mypublic, z);
return 0;
}

View File

@@ -1,6 +0,0 @@
#ifndef CURVE25519_DONNA_H
#define CURVE25519_DONNA_H
extern int curve25519_donna(uint8_t *, const uint8_t *, const uint8_t *);
#endif

Binary file not shown.

View File

@@ -1,20 +0,0 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -1,52 +0,0 @@
package textsecure;
option java_package = "org.whispersystems.textsecure.push";
option java_outer_classname = "PushMessageProtos";
message IncomingPushMessageSignal {
enum Type {
UNKNOWN = 0;
CIPHERTEXT = 1;
KEY_EXCHANGE = 2;
PREKEY_BUNDLE = 3;
PLAINTEXT = 4;
}
optional Type type = 1;
optional string source = 2;
optional uint32 sourceDevice = 7;
optional string relay = 3;
optional uint64 timestamp = 5;
optional bytes message = 6; // Contains an encrypted PushMessageContent
// repeated string destinations = 4; // No longer supported
}
message PushMessageContent {
message AttachmentPointer {
optional fixed64 id = 1;
optional string contentType = 2;
optional bytes key = 3;
}
message GroupContext {
enum Type {
UNKNOWN = 0;
UPDATE = 1;
DELIVER = 2;
QUIT = 3;
}
optional bytes id = 1;
optional Type type = 2;
optional string name = 3;
repeated string members = 4;
optional AttachmentPointer avatar = 5;
}
enum Flags {
END_SESSION = 1;
}
optional string body = 1;
repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3;
optional uint32 flags = 4;
}

View File

@@ -1,70 +0,0 @@
package textsecure;
option java_package = "org.whispersystems.textsecure.storage";
option java_outer_classname = "StorageProtos";
message SessionStructure {
message Chain {
optional bytes senderEphemeral = 1;
optional bytes senderEphemeralPrivate = 2;
message ChainKey {
optional uint32 index = 1;
optional bytes key = 2;
}
optional ChainKey chainKey = 3;
message MessageKey {
optional uint32 index = 1;
optional bytes cipherKey = 2;
optional bytes macKey = 3;
}
repeated MessageKey messageKeys = 4;
}
message PendingKeyExchange {
optional uint32 sequence = 1;
optional bytes localBaseKey = 2;
optional bytes localBaseKeyPrivate = 3;
optional bytes localEphemeralKey = 4;
optional bytes localEphemeralKeyPrivate = 5;
optional bytes localIdentityKey = 7;
optional bytes localIdentityKeyPrivate = 8;
}
message PendingPreKey {
optional uint32 preKeyId = 1;
optional bytes baseKey = 2;
}
optional uint32 sessionVersion = 1;
optional bytes localIdentityPublic = 2;
optional bytes remoteIdentityPublic = 3;
optional bytes rootKey = 4;
optional uint32 previousCounter = 5;
optional Chain senderChain = 6;
repeated Chain receiverChains = 7;
optional PendingKeyExchange pendingKeyExchange = 8;
optional PendingPreKey pendingPreKey = 9;
optional uint32 remoteRegistrationId = 10;
optional uint32 localRegistrationId = 11;
optional bool needsRefresh = 12;
}
message RecordStructure {
optional SessionStructure currentSession = 1;
repeated SessionStructure previousSessions = 2;
}
message PreKeyRecordStructure {
optional uint32 id = 1;
optional bytes publicKey = 2;
optional bytes privateKey = 3;
}

View File

@@ -1,3 +0,0 @@
all:
protoc --java_out=../src/ IncomingPushMessageSignal.proto WhisperTextProtocol.proto LocalStorageProtocol.proto

View File

@@ -1,26 +0,0 @@
package textsecure;
option java_package = "org.whispersystems.textsecure.crypto.protocol";
option java_outer_classname = "WhisperProtos";
message WhisperMessage {
optional bytes ephemeralKey = 1;
optional uint32 counter = 2;
optional uint32 previousCounter = 3;
optional bytes ciphertext = 4;
}
message PreKeyWhisperMessage {
optional uint32 registrationId = 5;
optional uint32 preKeyId = 1;
optional bytes baseKey = 2;
optional bytes identityKey = 3;
optional bytes message = 4; // WhisperMessage
}
message KeyExchangeMessage {
optional uint32 id = 1;
optional bytes baseKey = 2;
optional bytes ephemeralKey = 3;
optional bytes identityKey = 4;
}

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -1,159 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.whispersystems.textsecure.util.Hex;
import org.whispersystems.textsecure.util.Util;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Arrays;
/**
* Encrypts push attachments.
*
* @author Moxie Marlinspike
*/
public class AttachmentCipher {
static final int CIPHER_KEY_SIZE = 32;
static final int MAC_KEY_SIZE = 32;
private final SecretKeySpec cipherKey;
private final SecretKeySpec macKey;
private final Cipher cipher;
private final Mac mac;
public AttachmentCipher() {
this.cipherKey = initializeRandomCipherKey();
this.macKey = initializeRandomMacKey();
this.cipher = initializeCipher();
this.mac = initializeMac();
}
public AttachmentCipher(byte[] combinedKeyMaterial) {
byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE);
this.cipherKey = new SecretKeySpec(parts[0], "AES");
this.macKey = new SecretKeySpec(parts[1], "HmacSHA256");
this.cipher = initializeCipher();
this.mac = initializeMac();
}
public byte[] getCombinedKeyMaterial() {
return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded());
}
public byte[] encrypt(byte[] plaintext) {
try {
this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey);
this.mac.init(this.macKey);
byte[] ciphertext = this.cipher.doFinal(plaintext);
byte[] iv = this.cipher.getIV();
byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext));
return Util.combine(iv, ciphertext, mac);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public byte[] decrypt(byte[] ciphertext)
throws InvalidMacException, InvalidMessageException
{
try {
if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) {
throw new InvalidMessageException("Message too short!");
}
byte[][] ciphertextParts = Util.split(ciphertext,
this.cipher.getBlockSize(),
ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(),
this.mac.getMacLength());
this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength());
byte[] ourMac = this.mac.doFinal();
if (!Arrays.equals(ourMac, ciphertextParts[2])) {
throw new InvalidMacException("Mac doesn't match!");
}
this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey,
new IvParameterSpec(ciphertextParts[0]));
return cipher.doFinal(ciphertextParts[1]);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new InvalidMessageException(e);
} catch (ParseException e) {
throw new InvalidMessageException(e);
}
}
private Mac initializeMac() {
try {
Mac mac = Mac.getInstance("HmacSHA256");
return mac;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private Cipher initializeCipher() {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
return cipher;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
}
}
private SecretKeySpec initializeRandomCipherKey() {
byte[] key = new byte[CIPHER_KEY_SIZE];
Util.getSecureRandom().nextBytes(key);
return new SecretKeySpec(key, "AES");
}
private SecretKeySpec initializeRandomMacKey() {
byte[] key = new byte[MAC_KEY_SIZE];
Util.getSecureRandom().nextBytes(key);
return new SecretKeySpec(key, "HmacSHA256");
}
}

View File

@@ -1,215 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.whispersystems.textsecure.util.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Class for streaming an encrypted push attachment off disk.
*
* @author Moxie Marlinspike
*/
public class AttachmentCipherInputStream extends FileInputStream {
private static final int BLOCK_SIZE = 16;
private Cipher cipher;
private boolean done;
private long totalDataSize;
private long totalRead;
private byte[] overflowBuffer;
public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial)
throws IOException, InvalidMessageException
{
super(file);
try {
byte[][] parts = Util.split(combinedKeyMaterial,
AttachmentCipher.CIPHER_KEY_SIZE,
AttachmentCipher.MAC_KEY_SIZE);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(parts[1], "HmacSHA256"));
if (file.length() <= BLOCK_SIZE + mac.getMacLength()) {
throw new InvalidMessageException("Message shorter than crypto overhead!");
}
verifyMac(file, mac);
byte[] iv = new byte[BLOCK_SIZE];
readFully(iv);
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(parts[0], "AES"), new IvParameterSpec(iv));
this.done = false;
this.totalRead = 0;
this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidMacException e) {
throw new InvalidMessageException(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
}
}
@Override
public int read(byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
if (totalRead != totalDataSize) return readIncremental(buffer, offset, length);
else if (!done) return readFinal(buffer, offset, length);
else return -1;
}
private int readFinal(byte[] buffer, int offset, int length) throws IOException {
try {
int flourish = cipher.doFinal(buffer, offset);
done = true;
return flourish;
} catch (IllegalBlockSizeException e) {
Log.w("EncryptingPartInputStream", e);
throw new IOException("Illegal block size exception!");
} catch (ShortBufferException e) {
Log.w("EncryptingPartInputStream", e);
throw new IOException("Short buffer exception!");
} catch (BadPaddingException e) {
Log.w("EncryptingPartInputStream", e);
throw new IOException("Bad padding exception!");
}
}
private int readIncremental(byte[] buffer, int offset, int length) throws IOException {
int readLength = 0;
if (null != overflowBuffer) {
if (overflowBuffer.length > length) {
System.arraycopy(overflowBuffer, 0, buffer, offset, length);
overflowBuffer = Arrays.copyOfRange(overflowBuffer, length, overflowBuffer.length);
return length;
} else if (overflowBuffer.length == length) {
System.arraycopy(overflowBuffer, 0, buffer, offset, length);
overflowBuffer = null;
return length;
} else {
System.arraycopy(overflowBuffer, 0, buffer, offset, overflowBuffer.length);
readLength += overflowBuffer.length;
offset += readLength;
length -= readLength;
overflowBuffer = null;
}
}
if (length + totalRead > totalDataSize)
length = (int)(totalDataSize - totalRead);
byte[] internalBuffer = new byte[length];
int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize());
totalRead += read;
try {
int outputLen = cipher.getOutputSize(read);
if (outputLen <= length) {
readLength += cipher.update(internalBuffer, 0, read, buffer, offset);
return readLength;
}
byte[] transientBuffer = new byte[outputLen];
outputLen = cipher.update(internalBuffer, 0, read, transientBuffer, 0);
if (outputLen <= length) {
System.arraycopy(transientBuffer, 0, buffer, offset, outputLen);
readLength += outputLen;
} else {
System.arraycopy(transientBuffer, 0, buffer, offset, length);
overflowBuffer = Arrays.copyOfRange(transientBuffer, length, outputLen);
readLength += length;
}
return readLength;
} catch (ShortBufferException e) {
throw new AssertionError(e);
}
}
private void verifyMac(File file, Mac mac) throws FileNotFoundException, InvalidMacException {
try {
FileInputStream fin = new FileInputStream(file);
int remainingData = (int) file.length() - mac.getMacLength();
byte[] buffer = new byte[4096];
while (remainingData > 0) {
int read = fin.read(buffer, 0, Math.min(buffer.length, remainingData));
mac.update(buffer, 0, read);
remainingData -= read;
}
byte[] ourMac = mac.doFinal();
byte[] theirMac = new byte[mac.getMacLength()];
Util.readFully(fin, theirMac);
if (!Arrays.equals(ourMac, theirMac)) {
throw new InvalidMacException("MAC doesn't match!");
}
} catch (IOException e1) {
throw new InvalidMacException(e1);
}
}
private void readFully(byte[] buffer) throws IOException {
int offset = 0;
for (;;) {
int read = super.read(buffer, offset, buffer.length - offset);
if (read + offset < buffer.length) offset += read;
else return;
}
}
}

View File

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

View File

@@ -1,113 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.os.Parcel;
import android.os.Parcelable;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Hex;
/**
* A class for representing an identity key.
*
* @author Moxie Marlinspike
*/
public class IdentityKey implements Parcelable, SerializableKey {
public static final Parcelable.Creator<IdentityKey> CREATOR = new Parcelable.Creator<IdentityKey>() {
public IdentityKey createFromParcel(Parcel in) {
try {
return new IdentityKey(in);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public IdentityKey[] newArray(int size) {
return new IdentityKey[size];
}
};
public static final int NIST_SIZE = 1 + ECPublicKey.KEY_SIZE;
private ECPublicKey publicKey;
public IdentityKey(ECPublicKey publicKey) {
this.publicKey = publicKey;
}
public IdentityKey(Parcel in) throws InvalidKeyException {
int length = in.readInt();
byte[] serialized = new byte[length];
in.readByteArray(serialized);
initializeFromSerialized(serialized, 0);
}
public IdentityKey(byte[] bytes, int offset) throws InvalidKeyException {
initializeFromSerialized(bytes, offset);
}
public ECPublicKey getPublicKey() {
return publicKey;
}
private void initializeFromSerialized(byte[] bytes, int offset) throws InvalidKeyException {
if ((bytes[offset] & 0xff) == 1) {
this.publicKey = Curve.decodePoint(bytes, offset +1);
} else {
this.publicKey = Curve.decodePoint(bytes, offset);
}
}
public byte[] serialize() {
return publicKey.serialize();
}
public String getFingerprint() {
return Hex.toString(publicKey.serialize());
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof IdentityKey)) return false;
return publicKey.equals(((IdentityKey) other).getPublicKey());
}
@Override
public int hashCode() {
return publicKey.hashCode();
}
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
byte[] serialized = this.serialize();
dest.writeInt(serialized.length);
dest.writeByteArray(serialized);
}
}

View File

@@ -1,43 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
/**
* Holder for public and private identity key pair.
*
* @author Moxie Marlinspike
*/
public class IdentityKeyPair {
private final IdentityKey publicKey;
private final ECPrivateKey privateKey;
public IdentityKeyPair(IdentityKey publicKey, ECPrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public IdentityKey getPublicKey() {
return publicKey;
}
public ECPrivateKey getPrivateKey() {
return privateKey;
}
}

View File

@@ -1,40 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
public class InvalidKeyException extends Exception {
public InvalidKeyException() {
// TODO Auto-generated constructor stub
}
public InvalidKeyException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public InvalidKeyException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public InvalidKeyException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

@@ -1,40 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
public class InvalidMacException extends Exception {
public InvalidMacException() {
// TODO Auto-generated constructor stub
}
public InvalidMacException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public InvalidMacException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public InvalidMacException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

@@ -1,40 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
public class InvalidMessageException extends Exception {
public InvalidMessageException() {
// TODO Auto-generated constructor stub
}
public InvalidMessageException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public InvalidMessageException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public InvalidMessageException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

@@ -1,40 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
public class InvalidVersionException extends Exception {
public InvalidVersionException() {
// TODO Auto-generated constructor stub
}
public InvalidVersionException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public InvalidVersionException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public InvalidVersionException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

View File

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

View File

@@ -1,182 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import com.google.thoughtcrimegson.Gson;
import org.whispersystems.textsecure.crypto.ecc.Curve25519;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.util.Medium;
import org.whispersystems.textsecure.util.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
public class PreKeyUtil {
public static final int BATCH_SIZE = 100;
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
int preKeyIdOffset = getNextPreKeyId(context);
for (int i=0;i<BATCH_SIZE;i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
record.save();
records.add(record);
}
setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
return records;
}
public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) {
if (PreKeyRecord.hasRecord(context, Medium.MAX_VALUE)) {
try {
return new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE);
} catch (InvalidKeyIdException e) {
Log.w("PreKeyUtil", e);
PreKeyRecord.delete(context, Medium.MAX_VALUE);
}
}
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
record.save();
return record;
}
// public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
// List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
// File directory = getPreKeysDirectory(context);
// String[] keyRecordIds = directory.list();
//
// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
//
// for (String keyRecordId : keyRecordIds) {
// try {
// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
// }
// } catch (InvalidKeyIdException e) {
// Log.w("PreKeyUtil", e);
// new File(getPreKeysDirectory(context), keyRecordId).delete();
// } catch (NumberFormatException nfe) {
// Log.w("PreKeyUtil", nfe);
// new File(getPreKeysDirectory(context), keyRecordId).delete();
// }
// }
//
// return records;
// }
//
// public static void clearPreKeys(Context context) {
// File directory = getPreKeysDirectory(context);
// String[] keyRecords = directory.list();
//
// for (String keyRecord : keyRecords) {
// new File(directory, keyRecord).delete();
// }
// }
private static void setNextPreKeyId(Context context, int id) {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
FileOutputStream fout = new FileOutputStream(nextFile);
fout.write(new Gson().toJson(new PreKeyIndex(id)).getBytes());
fout.close();
} catch (IOException e) {
Log.w("PreKeyUtil", e);
}
}
private static int getNextPreKeyId(Context context) {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
if (!nextFile.exists()) {
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));
PreKeyIndex index = new Gson().fromJson(reader, PreKeyIndex.class);
reader.close();
return index.nextPreKeyId;
}
} catch (IOException e) {
Log.w("PreKeyUtil", e);
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
}
}
private static File getPreKeysDirectory(Context context) {
File directory = new File(context.getFilesDir(), PreKeyRecord.PREKEY_DIRECTORY);
if (!directory.exists())
directory.mkdirs();
return directory;
}
private static class PreKeyRecordIdComparator implements Comparator<String> {
@Override
public int compare(String lhs, String rhs) {
if (lhs.equals(PreKeyIndex.FILE_NAME)) return -1;
else if (rhs.equals(PreKeyIndex.FILE_NAME)) return 1;
try {
long lhsLong = Long.parseLong(lhs);
long rhsLong = Long.parseLong(rhs);
if (lhsLong < rhsLong) return -1;
else if (lhsLong > rhsLong) return 1;
else return 0;
} catch (NumberFormatException e) {
return 0;
}
}
}
private static class PreKeyIndex {
public static final String FILE_NAME = "index.dat";
private int nextPreKeyId;
public PreKeyIndex() {}
public PreKeyIndex(int nextPreKeyId) {
this.nextPreKeyId = nextPreKeyId;
}
}
}

View File

@@ -1,21 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
public interface SerializableKey {
public byte[] serialize();
}

View File

@@ -1,275 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
import org.whispersystems.textsecure.crypto.protocol.WhisperMessage;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import org.whispersystems.textsecure.util.Conversions;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class SessionCipher {
private static final Object SESSION_LOCK = new Object();
private final Context context;
private final MasterSecret masterSecret;
private final RecipientDevice recipient;
public static SessionCipher createFor(Context context,
MasterSecret masterSecret,
RecipientDevice recipient)
{
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
return new SessionCipher(context, masterSecret, recipient);
} else {
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
}
}
private SessionCipher(Context context, MasterSecret masterSecret, RecipientDevice recipient) {
this.recipient = recipient;
this.masterSecret = masterSecret;
this.context = context;
}
public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
ChainKey chainKey = sessionState.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
int previousCounter = sessionState.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessage(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody);
if (sessionState.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
int localRegistrationId = sessionState.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
pendingPreKey.second,
sessionState.getLocalIdentityKey(),
(WhisperMessage) ciphertextMessage);
}
sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionRecord.save();
return ciphertextMessage;
}
}
public byte[] decrypt(byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionRecord.save();
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
for (SessionState previousState : previousStates) {
try {
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionRecord.save();
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
}
throw new InvalidMessageException("No valid sessions.");
}
}
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionState.clearPendingPreKey();
return plaintext;
}
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
return sessionRecord.getSessionState().getRemoteRegistrationId();
}
}
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
throws InvalidMessageException
{
try {
if (sessionState.hasReceiverChain(theirEphemeral)) {
return sessionState.getReceiverChainKey(theirEphemeral);
} else {
RootKey rootKey = sessionState.getRootKey();
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionState.setRootKey(senderChain.first);
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
return receiverChain.second;
}
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
ECPublicKey theirEphemeral,
ChainKey chainKey, int counter)
throws InvalidMessageException, DuplicateMessageException
{
if (chainKey.getIndex() > counter) {
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
return sessionState.removeMessageKeys(theirEphemeral, counter);
} else {
throw new DuplicateMessageException("Received message with old counter: " +
chainKey.getIndex() + " , " + counter);
}
}
if (chainKey.getIndex() - counter > 2000) {
throw new InvalidMessageException("Over 2000 messages into the future!");
}
while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys();
sessionState.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey();
}
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
return chainKey.getMessageKeys();
}
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(plaintext);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
messageKeys.getCipherKey(),
messageKeys.getCounter());
return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e);
}
}
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] ivBytes = new byte[16];
Conversions.intToByteArray(ivBytes, 0, counter);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(mode, key, iv);
return cipher;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (java.security.InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
}
}
private SessionRecordV2 getSessionRecord() {
return new SessionRecordV2(context, masterSecret, recipient);
}
}

View File

@@ -1,28 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto;
import java.io.IOException;
public interface TransportDetails {
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding);
public byte[] getPaddedMessageBody(byte[] messageBody);
public byte[] getEncodedMessage(byte[] messageWithMac);
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException;
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
public class Curve {
public static final int DJB_TYPE = 0x05;
public static ECKeyPair generateKeyPair(boolean ephemeral) {
return Curve25519.generateKeyPair(ephemeral);
}
public static ECPublicKey decodePoint(byte[] bytes, int offset)
throws InvalidKeyException
{
int type = bytes[offset];
if (type == DJB_TYPE) {
return Curve25519.decodePoint(bytes, offset);
} else {
throw new InvalidKeyException("Unknown key type: " + type);
}
}
public static ECPrivateKey decodePrivatePoint(byte[] bytes) {
return new DjbECPrivateKey(bytes);
}
public static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey)
throws InvalidKeyException
{
if (publicKey.getType() != privateKey.getType()) {
throw new InvalidKeyException("Public and private keys must be of the same type!");
}
if (publicKey.getType() == DJB_TYPE) {
return Curve25519.calculateAgreement(publicKey, privateKey);
} else {
throw new InvalidKeyException("Unknown type: " + publicKey.getType());
}
}
}

View File

@@ -1,75 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class Curve25519 {
static {
System.loadLibrary("curve25519");
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static final SecureRandom random;
private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic);
private static native byte[] generatePublicKey(byte[] privateKey);
private static native byte[] generatePrivateKey(byte[] random, boolean ephemeral);
public static ECKeyPair generateKeyPair(boolean ephemeral) {
byte[] privateKey = generatePrivateKey(ephemeral);
byte[] publicKey = generatePublicKey(privateKey);
return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey));
}
static byte[] calculateAgreement(ECPublicKey publicKey, ECPrivateKey privateKey) {
return calculateAgreement(((DjbECPrivateKey)privateKey).getPrivateKey(),
((DjbECPublicKey)publicKey).getPublicKey());
}
static ECPublicKey decodePoint(byte[] encoded, int offset)
throws InvalidKeyException
{
int type = encoded[offset] & 0xFF;
byte[] keyBytes = new byte[32];
System.arraycopy(encoded, offset+1, keyBytes, 0, keyBytes.length);
if (type != Curve.DJB_TYPE) {
throw new InvalidKeyException("Bad key type: " + type);
}
return new DjbECPublicKey(keyBytes);
}
private static byte[] generatePrivateKey(boolean ephemeral) {
byte[] privateKey = new byte[32];
random.nextBytes(privateKey);
return generatePrivateKey(privateKey, ephemeral);
}
}

View File

@@ -1,66 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
import org.whispersystems.textsecure.util.Util;
import java.math.BigInteger;
import java.util.Arrays;
public class DjbECPublicKey implements ECPublicKey {
private final byte[] publicKey;
DjbECPublicKey(byte[] publicKey) {
this.publicKey = publicKey;
}
@Override
public byte[] serialize() {
byte[] type = {Curve.DJB_TYPE};
return Util.combine(type, publicKey);
}
@Override
public int getType() {
return Curve.DJB_TYPE;
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (!(other instanceof DjbECPublicKey)) return false;
DjbECPublicKey that = (DjbECPublicKey)other;
return Arrays.equals(this.publicKey, that.publicKey);
}
@Override
public int hashCode() {
return Arrays.hashCode(publicKey);
}
@Override
public int compareTo(ECPublicKey another) {
return new BigInteger(publicKey).compareTo(new BigInteger(((DjbECPublicKey)another).publicKey));
}
public byte[] getPublicKey() {
return publicKey;
}
}

View File

@@ -1,37 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.ecc;
public class ECKeyPair {
private final ECPublicKey publicKey;
private final ECPrivateKey privateKey;
public ECKeyPair(ECPublicKey publicKey, ECPrivateKey privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public ECPublicKey getPublicKey() {
return publicKey;
}
public ECPrivateKey getPrivateKey() {
return privateKey;
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import javax.crypto.spec.SecretKeySpec;
public class DerivedSecrets {
private final SecretKeySpec cipherKey;
private final SecretKeySpec macKey;
public DerivedSecrets(SecretKeySpec cipherKey, SecretKeySpec macKey) {
this.cipherKey = cipherKey;
this.macKey = macKey;
}
public SecretKeySpec getCipherKey() {
return cipherKey;
}
public SecretKeySpec getMacKey() {
return macKey;
}
}

View File

@@ -1,108 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class HKDF {
private static final int HASH_OUTPUT_SIZE = 32;
private static final int KEY_MATERIAL_SIZE = 64;
private static final int CIPHER_KEYS_OFFSET = 0;
private static final int MAC_KEYS_OFFSET = 32;
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] info) {
byte[] salt = new byte[HASH_OUTPUT_SIZE];
return deriveSecrets(inputKeyMaterial, salt, info);
}
public DerivedSecrets deriveSecrets(byte[] inputKeyMaterial, byte[] salt, byte[] info) {
byte[] prk = extract(salt, inputKeyMaterial);
byte[] okm = expand(prk, info, KEY_MATERIAL_SIZE);
SecretKeySpec cipherKey = deriveCipherKey(okm);
SecretKeySpec macKey = deriveMacKey(okm);
return new DerivedSecrets(cipherKey, macKey);
}
private SecretKeySpec deriveCipherKey(byte[] okm) {
byte[] cipherKey = new byte[32];
System.arraycopy(okm, CIPHER_KEYS_OFFSET, cipherKey, 0, cipherKey.length);
return new SecretKeySpec(cipherKey, "AES");
}
private SecretKeySpec deriveMacKey(byte[] okm) {
byte[] macKey = new byte[32];
System.arraycopy(okm, MAC_KEYS_OFFSET, macKey, 0, macKey.length);
return new SecretKeySpec(macKey, "HmacSHA256");
}
private byte[] extract(byte[] salt, byte[] inputKeyMaterial) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(salt, "HmacSHA256"));
return mac.doFinal(inputKeyMaterial);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
private byte[] expand(byte[] prk, byte[] info, int outputSize) {
try {
int iterations = (int)Math.ceil((double)outputSize/(double)HASH_OUTPUT_SIZE);
byte[] mixin = new byte[0];
ByteArrayOutputStream results = new ByteArrayOutputStream();
for (int i=0;i<iterations;i++) {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(prk, "HmacSHA256"));
mac.update(mixin);
if (info != null) {
mac.update(info);
}
mac.update((byte)i);
byte[] stepResult = mac.doFinal();
results.write(stepResult, 0, stepResult.length);
mixin = stepResult;
}
return results.toByteArray();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -1,86 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.crypto.kdf;
import android.util.Log;
import org.whispersystems.textsecure.util.Conversions;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.spec.SecretKeySpec;
public class NKDF {
public static final int LEGACY_CIPHER_KEY_LENGTH = 16;
public static final int LEGACY_MAC_KEY_LENGTH = 20;
public DerivedSecrets deriveSecrets(byte[] sharedSecret, boolean isLowEnd)
{
SecretKeySpec cipherKey = deriveCipherSecret(isLowEnd, sharedSecret);
SecretKeySpec macKey = deriveMacSecret(cipherKey);
return new DerivedSecrets(cipherKey, macKey);
}
private SecretKeySpec deriveCipherSecret(boolean isLowEnd, byte[] sharedSecret) {
byte[] derivedBytes = deriveBytes(sharedSecret, LEGACY_CIPHER_KEY_LENGTH * 2);
byte[] cipherSecret = new byte[LEGACY_CIPHER_KEY_LENGTH];
if (isLowEnd) {
System.arraycopy(derivedBytes, LEGACY_CIPHER_KEY_LENGTH, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH);
} else {
System.arraycopy(derivedBytes, 0, cipherSecret, 0, LEGACY_CIPHER_KEY_LENGTH);
}
return new SecretKeySpec(cipherSecret, "AES");
}
private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] secret = md.digest(key.getEncoded());
return new SecretKeySpec(secret, "HmacSHA1");
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("SHA-1 Not Supported!",e);
}
}
private byte[] deriveBytes(byte[] seed, int bytesNeeded) {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
Log.w("NKDF", e);
throw new IllegalArgumentException("SHA-256 Not Supported!");
}
int rounds = bytesNeeded / md.getDigestLength();
for (int i=1;i<=rounds;i++) {
byte[] roundBytes = Conversions.intToByteArray(i);
md.update(roundBytes);
md.update(seed);
}
return md.digest();
}
}

View File

@@ -1,17 +0,0 @@
package org.whispersystems.textsecure.crypto.protocol;
public interface CiphertextMessage {
public static final int UNSUPPORTED_VERSION = 1;
public static final int CURRENT_VERSION = 2;
public static final int WHISPER_TYPE = 2;
public static final int PREKEY_TYPE = 3;
// This should be the worst case (worse than V2). So not always accurate, but good enough for padding.
public static final int ENCRYPTED_MESSAGE_OVERHEAD = 53;
public byte[] serialize();
public int getType();
}

View File

@@ -1,115 +0,0 @@
package org.whispersystems.textsecure.crypto.protocol;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.LegacyMessageException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;
public class PreKeyWhisperMessage implements CiphertextMessage {
private final int version;
private final int registrationId;
private final int preKeyId;
private final ECPublicKey baseKey;
private final IdentityKey identityKey;
private final WhisperMessage message;
private final byte[] serialized;
public PreKeyWhisperMessage(byte[] serialized)
throws InvalidMessageException, InvalidVersionException
{
try {
this.version = Conversions.highBitsToInt(serialized[0]);
if (this.version > CiphertextMessage.CURRENT_VERSION) {
throw new InvalidVersionException("Unknown version: " + this.version);
}
WhisperProtos.PreKeyWhisperMessage preKeyWhisperMessage
= WhisperProtos.PreKeyWhisperMessage.parseFrom(ByteString.copyFrom(serialized, 1,
serialized.length-1));
if (!preKeyWhisperMessage.hasPreKeyId() ||
!preKeyWhisperMessage.hasBaseKey() ||
!preKeyWhisperMessage.hasIdentityKey() ||
!preKeyWhisperMessage.hasMessage())
{
throw new InvalidMessageException("Incomplete message.");
}
this.serialized = serialized;
this.registrationId = preKeyWhisperMessage.getRegistrationId();
this.preKeyId = preKeyWhisperMessage.getPreKeyId();
this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0);
this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0));
this.message = new WhisperMessage(preKeyWhisperMessage.getMessage().toByteArray());
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
} catch (LegacyMessageException e) {
throw new InvalidMessageException(e);
}
}
public PreKeyWhisperMessage(int registrationId, int preKeyId, ECPublicKey baseKey,
IdentityKey identityKey, WhisperMessage message)
{
this.version = CiphertextMessage.CURRENT_VERSION;
this.registrationId = registrationId;
this.preKeyId = preKeyId;
this.baseKey = baseKey;
this.identityKey = identityKey;
this.message = message;
byte[] versionBytes = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, this.version)};
byte[] messageBytes = WhisperProtos.PreKeyWhisperMessage.newBuilder()
.setPreKeyId(preKeyId)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
.setMessage(ByteString.copyFrom(message.serialize()))
.setRegistrationId(registrationId)
.build().toByteArray();
this.serialized = Util.combine(versionBytes, messageBytes);
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public int getRegistrationId() {
return registrationId;
}
public int getPreKeyId() {
return preKeyId;
}
public ECPublicKey getBaseKey() {
return baseKey;
}
public WhisperMessage getWhisperMessage() {
return message;
}
@Override
public byte[] serialize() {
return serialized;
}
@Override
public int getType() {
return CiphertextMessage.PREKEY_TYPE;
}
}

View File

@@ -1,141 +0,0 @@
package org.whispersystems.textsecure.crypto.protocol;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.LegacyMessageException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class WhisperMessage implements CiphertextMessage {
private static final int MAC_LENGTH = 8;
private final ECPublicKey senderEphemeral;
private final int counter;
private final int previousCounter;
private final byte[] ciphertext;
private final byte[] serialized;
public WhisperMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
try {
byte[][] messageParts = Util.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
byte version = messageParts[0][0];
byte[] message = messageParts[1];
byte[] mac = messageParts[2];
if (Conversions.highBitsToInt(version) <= CiphertextMessage.UNSUPPORTED_VERSION) {
throw new LegacyMessageException("Legacy message: " + Conversions.highBitsToInt(version));
}
if (Conversions.highBitsToInt(version) != CURRENT_VERSION) {
throw new InvalidMessageException("Unknown version: " + Conversions.highBitsToInt(version));
}
WhisperProtos.WhisperMessage whisperMessage = WhisperProtos.WhisperMessage.parseFrom(message);
if (!whisperMessage.hasCiphertext() ||
!whisperMessage.hasCounter() ||
!whisperMessage.hasEphemeralKey())
{
throw new InvalidMessageException("Incomplete message.");
}
this.serialized = serialized;
this.senderEphemeral = Curve.decodePoint(whisperMessage.getEphemeralKey().toByteArray(), 0);
this.counter = whisperMessage.getCounter();
this.previousCounter = whisperMessage.getPreviousCounter();
this.ciphertext = whisperMessage.getCiphertext().toByteArray();
} catch (InvalidProtocolBufferException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
} catch (ParseException e) {
throw new InvalidMessageException(e);
}
}
public WhisperMessage(SecretKeySpec macKey, ECPublicKey senderEphemeral,
int counter, int previousCounter, byte[] ciphertext)
{
byte[] version = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
byte[] message = WhisperProtos.WhisperMessage.newBuilder()
.setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize()))
.setCounter(counter)
.setPreviousCounter(previousCounter)
.setCiphertext(ByteString.copyFrom(ciphertext))
.build().toByteArray();
byte[] mac = getMac(macKey, Util.combine(version, message));
this.serialized = Util.combine(version, message, mac);
this.senderEphemeral = senderEphemeral;
this.counter = counter;
this.previousCounter = previousCounter;
this.ciphertext = ciphertext;
}
public ECPublicKey getSenderEphemeral() {
return senderEphemeral;
}
public int getCounter() {
return counter;
}
public byte[] getBody() {
return ciphertext;
}
public void verifyMac(SecretKeySpec macKey)
throws InvalidMessageException
{
byte[][] parts = Util.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH);
byte[] ourMac = getMac(macKey, parts[0]);
byte[] theirMac = parts[1];
if (!MessageDigest.isEqual(ourMac, theirMac)) {
throw new InvalidMessageException("Bad Mac!");
}
}
private byte[] getMac(SecretKeySpec macKey, byte[] serialized) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
byte[] fullMac = mac.doFinal(serialized);
return Util.trim(fullMac, MAC_LENGTH);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (java.security.InvalidKeyException e) {
throw new AssertionError(e);
}
}
@Override
public byte[] serialize() {
return serialized;
}
@Override
public int getType() {
return CiphertextMessage.WHISPER_TYPE;
}
public static boolean isLegacy(byte[] message) {
return message != null && message.length >= 1 &&
Conversions.highBitsToInt(message[0]) <= CiphertextMessage.UNSUPPORTED_VERSION;
}
}

View File

@@ -1,58 +0,0 @@
package org.whispersystems.textsecure.crypto.ratchet;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class ChainKey {
private static final byte[] MESSAGE_KEY_SEED = {0x01};
private static final byte[] CHAIN_KEY_SEED = {0x02};
private final byte[] key;
private final int index;
public ChainKey(byte[] key, int index) {
this.key = key;
this.index = index;
}
public byte[] getKey() {
return key;
}
public int getIndex() {
return index;
}
public ChainKey getNextChainKey() {
byte[] nextKey = getBaseMaterial(CHAIN_KEY_SEED);
return new ChainKey(nextKey, index + 1);
}
public MessageKeys getMessageKeys() {
HKDF kdf = new HKDF();
byte[] inputKeyMaterial = getBaseMaterial(MESSAGE_KEY_SEED);
DerivedSecrets keyMaterial = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes());
return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index);
}
private byte[] getBaseMaterial(byte[] seed) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(seed);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -1,28 +0,0 @@
package org.whispersystems.textsecure.crypto.ratchet;
import javax.crypto.spec.SecretKeySpec;
public class MessageKeys {
private final SecretKeySpec cipherKey;
private final SecretKeySpec macKey;
private final int counter;
public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) {
this.cipherKey = cipherKey;
this.macKey = macKey;
this.counter = counter;
}
public SecretKeySpec getCipherKey() {
return cipherKey;
}
public SecretKeySpec getMacKey() {
return macKey;
}
public int getCounter() {
return counter;
}
}

View File

@@ -1,121 +0,0 @@
package org.whispersystems.textsecure.crypto.ratchet;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF;
import org.whispersystems.textsecure.storage.SessionState;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class RatchetingSession {
public static void initializeSession(SessionState sessionState,
ECKeyPair ourBaseKey,
ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
} else {
initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey,
ourEphemeralKey, ourIdentityKey, theirIdentityKey);
}
}
private static void initializeSessionAsAlice(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPair(true);
Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey);
sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second);
sessionState.setSenderChain(sendingKey, sendingChain.second);
sessionState.setRootKey(sendingChain.first);
}
private static void initializeSessionAsBob(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey,
ourIdentityKey, theirIdentityKey);
sessionState.setSenderChain(ourEphemeralKey, sendingChain.second);
sessionState.setRootKey(sendingChain.first);
}
private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice,
ECKeyPair ourEphemeral, ECPublicKey theirEphemeral,
IdentityKeyPair ourIdentity, IdentityKey theirIdentity)
throws InvalidKeyException
{
try {
ByteArrayOutputStream secrets = new ByteArrayOutputStream();
if (isAlice) {
secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey()));
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey()));
} else {
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourEphemeral.getPrivateKey()));
secrets.write(Curve.calculateAgreement(theirEphemeral, ourIdentity.getPrivateKey()));
}
secrets.write(Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey()));
DerivedSecrets derivedSecrets = new HKDF().deriveSecrets(secrets.toByteArray(),
"WhisperText".getBytes());
return new Pair<RootKey, ChainKey>(new RootKey(derivedSecrets.getCipherKey().getEncoded()),
new ChainKey(derivedSecrets.getMacKey().getEncoded(), 0));
} catch (IOException e) {
throw new AssertionError(e);
}
}
private static boolean isAlice(ECPublicKey ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey ourEphemeralKey, ECPublicKey theirEphemeralKey)
{
if (ourEphemeralKey.equals(ourBaseKey)) {
return false;
}
if (theirEphemeralKey.equals(theirBaseKey)) {
return true;
}
return isLowEnd(ourBaseKey, theirBaseKey);
}
private static boolean isLowEnd(ECPublicKey ourKey, ECPublicKey theirKey) {
return ourKey.compareTo(theirKey) < 0;
}
}

View File

@@ -1,38 +0,0 @@
package org.whispersystems.textsecure.crypto.ratchet;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class RootKey {
private final byte[] key;
public RootKey(byte[] key) {
this.key = key;
}
public byte[] getKeyBytes() {
return key;
}
public Pair<RootKey, ChainKey> createChain(ECPublicKey theirEphemeral, ECKeyPair ourEphemeral)
throws InvalidKeyException
{
HKDF kdf = new HKDF();
byte[] sharedSecret = Curve.calculateAgreement(theirEphemeral, ourEphemeral.getPrivateKey());
DerivedSecrets keys = kdf.deriveSecrets(sharedSecret, key, "WhisperRatchet".getBytes());
RootKey newRootKey = new RootKey(keys.getCipherKey().getEncoded());
ChainKey newChainKey = new ChainKey(keys.getMacKey().getEncoded(), 0);
return new Pair<RootKey, ChainKey>(newRootKey, newChainKey);
}
}

View File

@@ -1,86 +0,0 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.directory;
import org.whispersystems.textsecure.util.Conversions;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* A simple bloom filter implementation that backs the RedPhone directory.
*
* @author Moxie Marlinspike
*
*/
public class BloomFilter {
private final MappedByteBuffer buffer;
private final long length;
private final int hashCount;
public BloomFilter(File bloomFilter, int hashCount)
throws IOException
{
this.length = bloomFilter.length();
this.buffer = new FileInputStream(bloomFilter).getChannel()
.map(FileChannel.MapMode.READ_ONLY, 0, length);
this.hashCount = hashCount;
}
public int getHashCount() {
return hashCount;
}
private boolean isBitSet(long bitIndex) {
int byteInQuestion = this.buffer.get((int)(bitIndex / 8));
int bitOffset = (0x01 << (bitIndex % 8));
return (byteInQuestion & bitOffset) > 0;
}
public boolean contains(String entity) {
try {
for (int i=0;i<this.hashCount;i++) {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec((i+"").getBytes(), "HmacSHA1"));
byte[] hashValue = mac.doFinal(entity.getBytes());
long bitIndex = Math.abs(Conversions.byteArrayToLong(hashValue, 0)) % (this.length * 8);
if (!isBitSet(bitIndex))
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -1,26 +0,0 @@
package org.whispersystems.textsecure.directory;
public class DirectoryDescriptor {
private String version;
private long capacity;
private int hashCount;
public String getUrl() {
return url;
}
public int getHashCount() {
return hashCount;
}
public long getCapacity() {
return capacity;
}
public String getVersion() {
return version;
}
private String url;
}

View File

@@ -1,225 +0,0 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.directory;
import android.content.Context;
import android.util.Log;
import com.google.thoughtcrimegson.Gson;
import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.annotations.SerializedName;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.GZIPInputStream;
/**
* Handles providing lookups, serializing, and deserializing the RedPhone directory.
*
* @author Moxie Marlinspike
*
*/
public class NumberFilter {
private static NumberFilter instance;
public synchronized static NumberFilter getInstance(Context context) {
if (instance == null)
instance = NumberFilter.deserializeFromFile(context);
return instance;
}
private static final String DIRECTORY_META_FILE = "directory.stat";
private File bloomFilter;
private String version;
private long capacity;
private int hashCount;
private Context context;
private NumberFilter(Context context, File bloomFilter, long capacity,
int hashCount, String version)
{
this.context = context.getApplicationContext();
this.bloomFilter = bloomFilter;
this.capacity = capacity;
this.hashCount = hashCount;
this.version = version;
}
public synchronized boolean containsNumber(String number) {
try {
if (bloomFilter == null) return false;
else if (number == null || number.length() == 0) return false;
return new BloomFilter(bloomFilter, hashCount).contains(number);
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
return false;
}
}
public synchronized boolean containsNumbers(List<String> numbers) {
try {
if (bloomFilter == null) return false;
if (numbers == null || numbers.size() == 0) return false;
BloomFilter filter = new BloomFilter(bloomFilter, hashCount);
for (String number : numbers) {
if (!filter.contains(number)) {
return false;
}
}
return true;
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
return false;
}
}
public synchronized void update(DirectoryDescriptor descriptor, File compressedData) {
try {
File uncompressed = File.createTempFile("directory", ".dat", context.getFilesDir());
FileInputStream fin = new FileInputStream (compressedData);
GZIPInputStream gin = new GZIPInputStream(fin);
FileOutputStream out = new FileOutputStream(uncompressed);
byte[] buffer = new byte[4096];
int read;
while ((read = gin.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.close();
compressedData.delete();
update(uncompressed, descriptor.getCapacity(), descriptor.getHashCount(), descriptor.getVersion());
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
}
}
private synchronized void update(File bloomFilter, long capacity, int hashCount, String version)
{
if (this.bloomFilter != null)
this.bloomFilter.delete();
this.bloomFilter = bloomFilter;
this.capacity = capacity;
this.hashCount = hashCount;
this.version = version;
serializeToFile(context);
}
private void serializeToFile(Context context) {
if (this.bloomFilter == null)
return;
try {
FileOutputStream fout = context.openFileOutput(DIRECTORY_META_FILE, 0);
NumberFilterStorage storage = new NumberFilterStorage(bloomFilter.getAbsolutePath(),
capacity, hashCount, version);
storage.serializeToStream(fout);
fout.close();
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
}
}
private static NumberFilter deserializeFromFile(Context context) {
try {
FileInputStream fis = context.openFileInput(DIRECTORY_META_FILE);
NumberFilterStorage storage = NumberFilterStorage.fromStream(fis);
if (storage == null) return new NumberFilter(context, null, 0, 0, "0");
else return new NumberFilter(context,
new File(storage.getDataPath()),
storage.getCapacity(),
storage.getHashCount(),
storage.getVersion());
} catch (IOException ioe) {
Log.w("NumberFilter", ioe);
return new NumberFilter(context, null, 0, 0, "0");
}
}
private static class NumberFilterStorage {
@SerializedName("data_path")
private String dataPath;
@SerializedName("capacity")
private long capacity;
@SerializedName("hash_count")
private int hashCount;
@SerializedName("version")
private String version;
public NumberFilterStorage(String dataPath, long capacity, int hashCount, String version) {
this.dataPath = dataPath;
this.capacity = capacity;
this.hashCount = hashCount;
this.version = version;
}
public String getDataPath() {
return dataPath;
}
public long getCapacity() {
return capacity;
}
public int getHashCount() {
return hashCount;
}
public String getVersion() {
return version;
}
public void serializeToStream(OutputStream out) throws IOException {
out.write(new Gson().toJson(this).getBytes());
}
public static NumberFilterStorage fromStream(InputStream in) throws IOException {
try {
return new Gson().fromJson(new BufferedReader(new InputStreamReader(in)),
NumberFilterStorage.class);
} catch (JsonParseException jpe) {
Log.w("NumberFilter", jpe);
throw new IOException("JSON Parse Exception");
}
}
}
}

View File

@@ -1,28 +0,0 @@
package org.whispersystems.textsecure.push;
public class AccountAttributes {
private String signalingKey;
private boolean supportsSms;
private int registrationId;
public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) {
this.signalingKey = signalingKey;
this.supportsSms = supportsSms;
this.registrationId = registrationId;
}
public AccountAttributes() {}
public String getSignalingKey() {
return signalingKey;
}
public boolean isSupportsSms() {
return supportsSms;
}
public int getRegistrationId() {
return registrationId;
}
}

View File

@@ -1,9 +0,0 @@
package org.whispersystems.textsecure.push;
import java.io.IOException;
public class AuthorizationFailedException extends IOException {
public AuthorizationFailedException(String s) {
super(s);
}
}

View File

@@ -1,33 +0,0 @@
package org.whispersystems.textsecure.push;
import com.google.thoughtcrimegson.Gson;
public class ContactTokenDetails {
private String token;
private String relay;
private String number;
private boolean supportsSms;
public ContactTokenDetails() {}
public String getToken() {
return token;
}
public String getRelay() {
return relay;
}
public boolean isSupportsSms() {
return supportsSms;
}
public void setNumber(String number) {
this.number = number;
}
public String getNumber() {
return number;
}
}

View File

@@ -1,14 +0,0 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class ContactTokenDetailsList {
private List<ContactTokenDetails> contacts;
public ContactTokenDetailsList() {}
public List<ContactTokenDetails> getContacts() {
return contacts;
}
}

View File

@@ -1,18 +0,0 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class ContactTokenList {
private List<String> contacts;
public ContactTokenList(List<String> contacts) {
this.contacts = contacts;
}
public ContactTokenList() {}
public List<String> getContacts() {
return contacts;
}
}

View File

@@ -1,6 +0,0 @@
package org.whispersystems.textsecure.push;
import java.io.IOException;
public class ExpectationFailedException extends IOException {
}

View File

@@ -1,136 +0,0 @@
package org.whispersystems.textsecure.push;
import android.util.Log;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
import org.whispersystems.textsecure.util.Hex;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class IncomingEncryptedPushMessage {
private static final int SUPPORTED_VERSION = 1;
private static final int CIPHER_KEY_SIZE = 32;
private static final int MAC_KEY_SIZE = 20;
private static final int MAC_SIZE = 10;
private static final int VERSION_OFFSET = 0;
private static final int VERSION_LENGTH = 1;
private static final int IV_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
private static final int IV_LENGTH = 16;
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
private final IncomingPushMessage incomingPushMessage;
public IncomingEncryptedPushMessage(String message, String signalingKey)
throws IOException, InvalidVersionException
{
byte[] ciphertext = Base64.decode(message);
if (ciphertext.length < VERSION_LENGTH || ciphertext[VERSION_OFFSET] != SUPPORTED_VERSION)
throw new InvalidVersionException("Unsupported version!");
SecretKeySpec cipherKey = getCipherKey(signalingKey);
SecretKeySpec macKey = getMacKey(signalingKey);
verifyMac(ciphertext, macKey);
byte[] plaintext = getPlaintext(ciphertext, cipherKey);
IncomingPushMessageSignal signal = IncomingPushMessageSignal.parseFrom(plaintext);
this.incomingPushMessage = new IncomingPushMessage(signal);
}
public IncomingPushMessage getIncomingPushMessage() {
return incomingPushMessage;
}
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
try {
byte[] ivBytes = new byte[IV_LENGTH];
System.arraycopy(ciphertext, IV_OFFSET, ivBytes, 0, ivBytes.length);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv);
return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET,
ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
Log.w("IncomingEncryptedPushMessage", e);
throw new IOException("Bad padding?");
}
}
private void verifyMac(byte[] ciphertext, SecretKeySpec macKey) throws IOException {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
if (ciphertext.length < MAC_SIZE + 1)
throw new IOException("Invalid MAC!");
mac.update(ciphertext, 0, ciphertext.length - MAC_SIZE);
byte[] ourMacFull = mac.doFinal();
byte[] ourMacBytes = new byte[MAC_SIZE];
System.arraycopy(ourMacFull, 0, ourMacBytes, 0, ourMacBytes.length);
byte[] theirMacBytes = new byte[MAC_SIZE];
System.arraycopy(ciphertext, ciphertext.length-MAC_SIZE, theirMacBytes, 0, theirMacBytes.length);
Log.w("IncomingEncryptedPushMessage", "Our MAC: " + Hex.toString(ourMacBytes));
Log.w("IncomingEncryptedPushMessage", "Thr MAC: " + Hex.toString(theirMacBytes));
if (!Arrays.equals(ourMacBytes, theirMacBytes)) {
throw new IOException("Invalid MAC compare!");
}
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
private SecretKeySpec getCipherKey(String signalingKey) throws IOException {
byte[] signalingKeyBytes = Base64.decode(signalingKey);
byte[] cipherKey = new byte[CIPHER_KEY_SIZE];
System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length);
return new SecretKeySpec(cipherKey, "AES");
}
private SecretKeySpec getMacKey(String signalingKey) throws IOException {
byte[] signalingKeyBytes = Base64.decode(signalingKey);
byte[] macKey = new byte[MAC_KEY_SIZE];
System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length);
return new SecretKeySpec(macKey, "HmacSHA256");
}
}

View File

@@ -1,141 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.push;
import android.os.Parcel;
import android.os.Parcelable;
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
public class IncomingPushMessage implements Parcelable {
public static final Parcelable.Creator<IncomingPushMessage> CREATOR = new Parcelable.Creator<IncomingPushMessage>() {
@Override
public IncomingPushMessage createFromParcel(Parcel in) {
return new IncomingPushMessage(in);
}
@Override
public IncomingPushMessage[] newArray(int size) {
return new IncomingPushMessage[size];
}
};
private int type;
private String source;
private int sourceDevice;
private byte[] message;
private long timestamp;
private String relay;
private IncomingPushMessage(IncomingPushMessage message, byte[] body) {
this.type = message.type;
this.source = message.source;
this.sourceDevice = message.sourceDevice;
this.timestamp = message.timestamp;
this.relay = message.relay;
this.message = body;
}
public IncomingPushMessage(IncomingPushMessageSignal signal) {
this.type = signal.getType().getNumber();
this.source = signal.getSource();
this.sourceDevice = signal.getSourceDevice();
this.message = signal.getMessage().toByteArray();
this.timestamp = signal.getTimestamp();
this.relay = signal.getRelay();
}
public IncomingPushMessage(Parcel in) {
this.type = in.readInt();
this.source = in.readString();
this.sourceDevice = in.readInt();
if (in.readInt() == 1) {
this.relay = in.readString();
}
this.message = new byte[in.readInt()];
in.readByteArray(this.message);
this.timestamp = in.readLong();
}
public IncomingPushMessage(int type, String source, int sourceDevice,
byte[] body, long timestamp)
{
this.type = type;
this.source = source;
this.sourceDevice = sourceDevice;
this.message = body;
this.timestamp = timestamp;
}
public String getRelay() {
return relay;
}
public long getTimestampMillis() {
return timestamp;
}
public String getSource() {
return source;
}
public int getSourceDevice() {
return sourceDevice;
}
public byte[] getBody() {
return message;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(source);
dest.writeInt(sourceDevice);
dest.writeInt(relay == null ? 0 : 1);
if (relay != null) {
dest.writeString(relay);
}
dest.writeInt(message.length);
dest.writeByteArray(message);
dest.writeLong(timestamp);
}
public IncomingPushMessage withBody(byte[] body) {
return new IncomingPushMessage(this, body);
}
public int getType() {
return type;
}
public boolean isSecureMessage() {
return getType() == IncomingPushMessageSignal.Type.CIPHERTEXT_VALUE;
}
public boolean isPreKeyBundle() {
return getType() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
}
}

View File

@@ -1,17 +0,0 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class MismatchedDevices {
private List<Integer> missingDevices;
private List<Integer> extraDevices;
public List<Integer> getMissingDevices() {
return missingDevices;
}
public List<Integer> getExtraDevices() {
return extraDevices;
}
}

View File

@@ -1,16 +0,0 @@
package org.whispersystems.textsecure.push;
import java.io.IOException;
public class MismatchedDevicesException extends IOException {
private final MismatchedDevices mismatchedDevices;
public MismatchedDevicesException(MismatchedDevices mismatchedDevices) {
this.mismatchedDevices = mismatchedDevices;
}
public MismatchedDevices getMismatchedDevices() {
return mismatchedDevices;
}
}

View File

@@ -1,9 +0,0 @@
package org.whispersystems.textsecure.push;
import java.io.IOException;
public class NotFoundException extends IOException {
public NotFoundException(String s) {
super(s);
}
}

View File

@@ -1,50 +0,0 @@
/**
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecure.push;
import org.whispersystems.textsecure.util.Base64;
public class OutgoingPushMessage {
private int type;
private int destinationDeviceId;
private int destinationRegistrationId;
private String body;
public OutgoingPushMessage(PushAddress address, PushBody body) {
this.type = body.getType();
this.destinationDeviceId = address.getDeviceId();
this.destinationRegistrationId = body.getRemoteRegistrationId();
this.body = Base64.encodeBytes(body.getBody());
}
public int getDestinationDeviceId() {
return destinationDeviceId;
}
public String getBody() {
return body;
}
public int getType() {
return type;
}
public int getDestinationRegistrationId() {
return destinationRegistrationId;
}
}

View File

@@ -1,31 +0,0 @@
package org.whispersystems.textsecure.push;
import java.util.LinkedList;
import java.util.List;
public class OutgoingPushMessageList {
private String destination;
private String relay;
private List<OutgoingPushMessage> messages;
public OutgoingPushMessageList(String destination, String relay, List<OutgoingPushMessage> messages) {
this.destination = destination;
this.relay = relay;
this.messages = messages;
}
public String getDestination() {
return destination;
}
public List<OutgoingPushMessage> getMessages() {
return messages;
}
public String getRelay() {
return relay;
}
}

View File

@@ -1,126 +0,0 @@
package org.whispersystems.textsecure.push;
import com.google.thoughtcrimegson.GsonBuilder;
import com.google.thoughtcrimegson.JsonDeserializationContext;
import com.google.thoughtcrimegson.JsonDeserializer;
import com.google.thoughtcrimegson.JsonElement;
import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.JsonPrimitive;
import com.google.thoughtcrimegson.JsonSerializationContext;
import com.google.thoughtcrimegson.JsonSerializer;
import com.google.thoughtcrimegson.annotations.Expose;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.util.Base64;
import java.io.IOException;
import java.lang.reflect.Type;
public class PreKeyEntity {
@Expose(serialize = false)
private int deviceId;
private int keyId;
private ECPublicKey publicKey;
private IdentityKey identityKey;
private int registrationId;
public PreKeyEntity(int keyId, ECPublicKey publicKey, IdentityKey identityKey) {
this.keyId = keyId;
this.publicKey = publicKey;
this.identityKey = identityKey;
this.registrationId = registrationId;
}
public int getDeviceId() {
return deviceId;
}
public int getKeyId() {
return keyId;
}
public ECPublicKey getPublicKey() {
return publicKey;
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public int getRegistrationId() {
return registrationId;
}
public static String toJson(PreKeyEntity entity) {
return getBuilder().create().toJson(entity);
}
public static PreKeyEntity fromJson(String encoded) {
return getBuilder().create().fromJson(encoded, PreKeyEntity.class);
}
public static GsonBuilder getBuilder() {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(ECPublicKey.class, new ECPublicKeyJsonAdapter());
builder.registerTypeAdapter(IdentityKey.class, new IdentityKeyJsonAdapter());
return builder;
}
private static class ECPublicKeyJsonAdapter
implements JsonSerializer<ECPublicKey>, JsonDeserializer<ECPublicKey>
{
@Override
public JsonElement serialize(ECPublicKey preKeyPublic, Type type,
JsonSerializationContext jsonSerializationContext)
{
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(preKeyPublic.serialize()));
}
@Override
public ECPublicKey deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException
{
try {
return Curve.decodePoint(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
} catch (InvalidKeyException e) {
throw new JsonParseException(e);
} catch (IOException e) {
throw new JsonParseException(e);
}
}
}
private static class IdentityKeyJsonAdapter
implements JsonSerializer<IdentityKey>, JsonDeserializer<IdentityKey>
{
@Override
public JsonElement serialize(IdentityKey identityKey, Type type,
JsonSerializationContext jsonSerializationContext)
{
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(identityKey.serialize()));
}
@Override
public IdentityKey deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException
{
try {
return new IdentityKey(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
} catch (InvalidKeyException e) {
throw new JsonParseException(e);
} catch (IOException e) {
throw new JsonParseException(e);
}
}
}
}

View File

@@ -1,30 +0,0 @@
package org.whispersystems.textsecure.push;
import java.util.List;
public class PreKeyList {
private PreKeyEntity lastResortKey;
private List<PreKeyEntity> keys;
public PreKeyList(PreKeyEntity lastResortKey, List<PreKeyEntity> keys) {
this.keys = keys;
this.lastResortKey = lastResortKey;
}
public List<PreKeyEntity> getKeys() {
return keys;
}
public static String toJson(PreKeyList entity) {
return PreKeyEntity.getBuilder().create().toJson(entity);
}
public static PreKeyList fromJson(String serialized) {
return PreKeyEntity.getBuilder().create().fromJson(serialized, PreKeyList.class);
}
public PreKeyEntity getLastResortKey() {
return lastResortKey;
}
}

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