diff --git a/Graphics/android-icon-copier/.gitignore b/Graphics/android-icon-copier/.gitignore
new file mode 100644
index 000000000..3336b9152
--- /dev/null
+++ b/Graphics/android-icon-copier/.gitignore
@@ -0,0 +1,81 @@
+# Python
+
+*.py[cod]
+
+# Options
+/options.json
+
+# Packages
+*.egg
+*.egg-info
+/dist
+/build
+/eggs
+/parts
+/bin
+/var
+/sdist
+/develop-eggs
+/lib
+/lib64
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+### Generic
+
+*.log
+*.sqlite?
+
+### Compiled binaries
+
+*.class
+*.jar
+
+*.o
+*.bin
+*.a
+*.lib
+*.so
+*.out
+
+*.obj
+*.exe
+*.dll
+*.com
+
+### *nix OS / apps
+
+*.swp
+*~
+
+### Mac OS generated
+
+__MACOSX
+Icon?
+*.DS_Store
+*.DS_Store?
+._*
+.Spotlight*
+.Trashes
+
+### Windows generated
+
+ehthumbs.db
+thumbs.db
+Thumbs.db
+
diff --git a/Graphics/android-icon-copier/LICENSE b/Graphics/android-icon-copier/LICENSE
new file mode 100644
index 000000000..4dc1175a5
--- /dev/null
+++ b/Graphics/android-icon-copier/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Lucas Tan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Graphics/android-icon-copier/README.md b/Graphics/android-icon-copier/README.md
new file mode 100644
index 000000000..3e12ae472
--- /dev/null
+++ b/Graphics/android-icon-copier/README.md
@@ -0,0 +1,109 @@
+[](https://android-arsenal.com/details/1/1325)
+
+What is this
+============
+A commandline tool to copy Android Material Design and FontAwesome icons to your
+ project folders: `drawable-ldpi`, `drawable-mdpi`, and etc.
+
+How it works
+============
+It downloads from these repos:
+- Material design: https://github.com/google/material-design-icons
+- FontAwesome and "Classic" Android: https://github.com/svenkapudija/Android-Action-Bar-Icons
+
+Resolution supported
+====================
+ | l | m | h | xh | xxh | xxxh |
+--------|---|---|---|----|-----|------|
+FA | Y | Y | Y | Y | Y | - |
+Classic | - | Y | Y | Y | Y | - |
+Material| - | Y | Y | Y | Y | Y |
+
+Sizes supported
+===============
+Material: 18, 24, 36, 48 dp.
+FA and Classic: 32 dp only.
+
+
+Usage
+=====
+
+Usage:
+Material : ./copy {proj path} {category} {color} {icon name} [size]
+Classic and FA: ./copy {proj path} {fa/classic} {color} {icon name}
+
+
+`[]` denotes optional args.
+**Args are case sensitive!**
+
+- `proj path`: Path to project folder relative to `base path`.
+ - `base path` can be defined in options file (see below).
+ - Auto-detects new or old project structure: `MyProject/src/main/res` or
+ `MyProject/res`.
+- `category`: Either "classic", "fa", or Material category.
+- `color`: Color of icon: Either "white", "grey" or "black".
+ - For Classic and FA, "white" refers to the Holo Dark theme (dark background).
+ "Grey" refers to the Holo Light theme.
+ - For Material, "grey" refers to grey600.
+- `icon name`: Name of icon (must replace spaces and dashes with underscores).
+ - Without any prefix. Examples: FontAwesome "thumbs_up", Classic "search".
+- `size` (integer): for Material only, Size in dp, defaults to 24 which is the
+ action bar icon size for material design.
+
+Examples
+--------
+- `./copy MyProject maps white place`
+ - Downloads to `BasePath/MyProject/{src/main}/res/drawable-{m,h,xh,xxh,xxh}dpi
+- `./copy MyProject maps white place 48`
+- `./copy Path/to/MyProject fa grey thumbs_up`
+
+Windows users need to use `python copy` instead (I think).
+
+Filename mapping
+================
+The tool also supports filename mapping of destination png files. (see options)
+Mapping vars:
+
+- `cat`: category
+- `name`: name as specified in commandline.
+- `color`: color as specified: white, black, grey.
+- `size`: integer only.
+- `bg`: derived from color. black => bright, white => dark, grey => light.
+- `bgSuffix`: "_dark" if bg is dark else empty string.
+
+Options file
+============
+Named `options.json` in same dir. Sample:
+```json
+{
+ "basePath": "~/Documents",
+ "filenameMap": {
+ "classic": "ic_action_{name}{bgSuffix}.png",
+ "fa": "ic_action_fa_{name}{bgSuffix}.png",
+ "material": "ic_{name}_{color}_{size}dp.png"
+ }
+}
+```
+
+~ is expanded to the user home dir.
+
+`./copy Path/to/MyProject fa white thumbs_up` results in the
+target filename of `ic_action_fa_thumbs_up_dark.png`.
+
+Installation
+============
+- Python >= 2.7 (older or newer ver might work, you may try.)
+- Python Requests package: `pip install requests`
+- Git clone this repo or download the script.
+
+Icon cheatsheet
+===============
+- Material: http://google.github.io/material-design-icons/
+- FA: http://fortawesome.github.io/Font-Awesome/icons/ (icons in 4.2 not supported)
+- Classic: coming soon.
+
+License
+=======
+This project is under the MIT License. (see LICENSE)
+
+Please refer to the respective icon library for its licensing info.
diff --git a/Graphics/android-icon-copier/classic.py b/Graphics/android-icon-copier/classic.py
new file mode 100644
index 000000000..17ae1aac4
--- /dev/null
+++ b/Graphics/android-icon-copier/classic.py
@@ -0,0 +1,146 @@
+# Maps icon name to the dir name.
+CLASSIC_MAP = {
+ 'about': '13_extra_actions_about',
+ 'accept': '01_core_accept',
+ 'accounts': '10_device_access_accounts',
+ 'add_alarm': '10_device_access_add_alarm',
+ 'add_group': '06_social_add_group',
+ 'add_person': '06_social_add_person',
+ 'add_to_queue': '09_media_add_to_queue',
+ 'airplane_mode_off': '10_device_access_airplane_mode_off',
+ 'airplane_mode_on': '10_device_access_airplane_mode_on',
+ 'alarms': '10_device_access_alarms',
+ 'attachment': '05_content_attachment',
+ 'back': '02_navigation_back',
+ 'backspace': '05_content_backspace',
+ 'bad': '03_rating_bad',
+ 'battery': '10_device_access_battery',
+ 'bightness_low': '10_device_access_bightness_low',
+ 'bluetooth': '10_device_access_bluetooth',
+ 'bluetooth_connected': '10_device_access_bluetooth_connected',
+ 'bluetooth_searching': '10_device_access_bluetooth_searching',
+ 'brightness_auto': '10_device_access_brightness_auto',
+ 'brightness_high': '10_device_access_brightness_high',
+ 'brightness_medium': '10_device_access_brightness_medium',
+ 'call': '01_core_call',
+ 'camera': '08_camera_camera',
+ 'cancel': '01_core_cancel',
+ 'cast': '09_media_cast',
+ 'cc_bcc': '06_social_cc_bcc',
+ 'chat': '06_social_chat',
+ 'cloud': '04_collections_cloud',
+ 'collapse': '02_navigation_collapse',
+ 'collection': '04_collections_collection',
+ 'computer': '11_hardware_computer',
+ 'copy': '01_core_copy',
+ 'crop': '08_camera_crop',
+ 'cut': '01_core_cut',
+ 'data_usage': '10_device_access_data_usage',
+ 'dial_pad': '10_device_access_dial_pad',
+ 'directions': '07_location_directions',
+ 'discard': '01_core_discard',
+ 'dock': '11_hardware_dock',
+ 'download': '09_media_download',
+ 'edit': '01_core_edit',
+ 'email': '05_content_email',
+ 'end_call': '10_device_access_end_call',
+ 'error': '12_alerts_and_states_error',
+ 'event': '05_content_event',
+ 'expand': '02_navigation_expand',
+ 'fast_forward': '09_media_fast_forward',
+ 'favorite': '03_rating_favorite',
+ 'flash_automatic': '08_camera_flash_automatic',
+ 'flash_off': '08_camera_flash_off',
+ 'flash_on': '08_camera_flash_on',
+ 'forward': '06_social_forward',
+ 'full_screen': '09_media_full_screen',
+ 'gamepad': '11_hardware_gamepad',
+ 'go_to_today': '04_collections_go_to_today',
+ 'good': '03_rating_good',
+ 'group': '06_social_group',
+ 'half_important': '03_rating_half_important',
+ 'headphones': '11_hardware_headphones',
+ 'headset': '11_hardware_headset',
+ 'help': '13_extra_actions_help',
+ 'import_export': '05_content_import_export',
+ 'important': '03_rating_important',
+ 'keyboard': '11_hardware_keyboard',
+ 'labels': '04_collections_labels',
+ 'location_found': '07_location_location_found',
+ 'location_off': '07_location_location_off',
+ 'location_searching': '07_location_location_searching',
+ 'make_available_offline': '09_media_make_available_offline',
+ 'map': '07_location_map',
+ 'merge': '05_content_merge',
+ 'mic': '08_camera_mic',
+ 'mic_muted': '08_camera_mic_muted',
+ 'mouse': '11_hardware_mouse',
+ 'network_cell': '10_device_access_network_cell',
+ 'network_wifi': '10_device_access_network_wifi',
+ 'new': '01_core_new',
+ 'new_account': '10_device_access_new_account',
+ 'new_attachment': '05_content_new_attachment',
+ 'new_email': '05_content_new_email',
+ 'new_event': '05_content_new_event',
+ 'new_label': '04_collections_new_label',
+ 'new_picture': '05_content_new_picture',
+ 'next': '09_media_next',
+ 'next_item': '02_navigation_next_item',
+ 'not_important': '03_rating_not_important',
+ 'not_secure': '10_device_access_not_secure',
+ 'overflow': '01_core_overflow',
+ 'paste': '01_core_paste',
+ 'pause': '09_media_pause',
+ 'pause_over_video': '09_media_pause_over_video',
+ 'person': '06_social_person',
+ 'phone': '11_hardware_phone',
+ 'picture': '05_content_picture',
+ 'place': '07_location_place',
+ 'play': '09_media_play',
+ 'play_over_video': '09_media_play_over_video',
+ 'previous': '09_media_previous',
+ 'previous_item': '02_navigation_previous_item',
+ 'read': '05_content_read',
+ 'refresh': '01_core_refresh',
+ 'remove': '01_core_remove',
+ 'repeat': '09_media_repeat',
+ 'replay': '09_media_replay',
+ 'reply': '06_social_reply',
+ 'reply_all': '06_social_reply_all',
+ 'return_from_full_screen': '09_media_return_from_full_screen',
+ 'rewind': '09_media_rewind',
+ 'ring_volume': '10_device_access_ring_volume',
+ 'rotate_left': '08_camera_rotate_left',
+ 'rotate_right': '08_camera_rotate_right',
+ 'save': '05_content_save',
+ 'screen_locked_to_landscape': '10_device_access_screen_locked_to_landscape',
+ 'screen_locked_to_portrait': '10_device_access_screen_locked_to_portrait',
+ 'screen_rotation': '10_device_access_screen_rotation',
+ 'sd_storage': '10_device_access_sd_storage',
+ 'search': '01_core_search',
+ 'secure': '10_device_access_secure',
+ 'select_all': '01_core_select_all',
+ 'send_now': '06_social_send_now',
+ 'settings': '13_extra_actions_settings',
+ 'share': '01_core_share',
+ 'shuffle': '09_media_shuffle',
+ 'slideshow': '09_media_slideshow',
+ 'sort_by_size': '04_collections_sort_by_size',
+ 'split': '05_content_split',
+ 'stop': '09_media_stop',
+ 'storage': '10_device_access_storage',
+ 'switch_camera': '08_camera_switch_camera',
+ 'switch_video': '08_camera_switch_video',
+ 'time': '10_device_access_time',
+ 'undo': '01_core_undo',
+ 'unread': '05_content_unread',
+ 'upload': '09_media_upload',
+ 'usb': '10_device_access_usb',
+ 'video': '08_camera_video',
+ 'view_as_grid': '04_collections_view_as_grid',
+ 'view_as_list': '04_collections_view_as_list',
+ 'volume_muted': '09_media_volume_muted',
+ 'volume_on': '09_media_volume_on',
+ 'warning': '12_alerts_and_states_warning',
+ 'web_site': '07_location_web_site',
+}
diff --git a/Graphics/android-icon-copier/copy b/Graphics/android-icon-copier/copy
new file mode 100755
index 000000000..995a8789e
--- /dev/null
+++ b/Graphics/android-icon-copier/copy
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import shutil
+import requests
+from os.path import expanduser
+import classic
+
+resolutions = {
+ 'material': ("m", "h", "xh", "xxh", "xxxh"),
+ 'fa': ("l", "m", "h", "xh", "xxh"),
+ 'classic': ("m", "h", "xh", "xxh"),
+}
+
+
+class AppError(Exception):
+ pass
+
+
+def make_filename(filename_format, cat, name, color, size):
+ args = {
+ 'cat': cat or '',
+ 'name': name,
+ 'color': color,
+ 'size': size,
+ }
+ bg = {'white': 'dark', 'grey': 'light', 'black': 'bright'}.get(color) or ''
+ bg_suffix = '_dark' if bg == 'dark' else ''
+ args['bgSuffix'] = bg_suffix
+ args['bg'] = bg
+ return filename_format.format(**args)
+
+
+def download_url(url, target_path):
+ print("Downloading {} to {} ...".format(url, target_path))
+ print("")
+ #r = requests.get(url, stream=True)
+ r = requests.get(url)
+ if r.status_code != 200:
+ raise AppError("url not found, perhaps invalid name, size or color")
+ with open(target_path, 'wb') as fd:
+ for chunk in r.iter_content(4096):
+ fd.write(chunk)
+
+
+def make_material_icon_url(cat, res, name, color, size):
+ if color == 'grey':
+ color = 'grey600'
+ elif color not in ('white', 'black'):
+ raise AppError('invalid color')
+ return ('https://raw.githubusercontent.com/google/material-design-icons/master/' +
+ '{}/drawable-{}dpi/ic_{}_{}_{}dp.png').format(cat, res, name, color, size)
+
+
+def make_fa_icon_url(res, name, color):
+ if color == 'white':
+ holo = 'dark'
+ elif color == 'grey':
+ holo = 'light'
+ else:
+ raise AppError('invalid color')
+ return ('https://raw.githubusercontent.com/svenkapudija/Android-Action-Bar-Icons/' +
+ 'master/Font Awesome/holo_{2}/ic_fa_{1}/drawable-{0}dpi/ic_fa_{1}.png').format(res, name, holo)
+
+
+def make_classic_icon_url(res, name, color):
+ dirname = classic.CLASSIC_MAP.get(name)
+ if not dirname:
+ raise AppError('invalid name')
+ if color == 'white':
+ holo = 'dark'
+ elif color == 'grey':
+ holo = 'light'
+ else:
+ raise AppError('invalid color')
+ return ('https://raw.githubusercontent.com/svenkapudija/Android-Action-Bar-Icons/' +
+ 'master/Android Stock/holo_{2}/{3}/drawable-{0}dpi/ic_action_{1}.png').format(res, name, holo, dirname)
+
+
+def make_target_path(base_path, proj, res, filename):
+ res_path1 = os.path.join(base_path, proj, 'src', 'main', 'res')
+ res_path2 = os.path.join(base_path, proj, 'res')
+ if os.path.isdir(res_path1):
+ res_path = res_path1
+ elif os.path.isdir(res_path2):
+ res_path = res_path2
+ else:
+ raise AppError('missing res dir')
+ res_specific_path = os.path.join(res_path, 'drawable-' + res + 'dpi')
+ try:
+ os.mkdir(res_specific_path)
+ except OSError:
+ pass
+ return os.path.join(res_specific_path, filename)
+
+
+def do_material(options, proj_path, cat, name, color, size):
+ base_path = expanduser(options['basePath'])
+ filename_map = options['filenameMap']
+
+ for res in resolutions['material']:
+ filename = make_filename(filename_map['material'], cat, name, color, size)
+ target_path = make_target_path(base_path, proj_path, res, filename)
+ url = make_material_icon_url(cat, res, name, color, size)
+ download_url(url, target_path)
+
+
+def do_classic_or_fa(options, proj_path, cat, name, color):
+ base_path = expanduser(options['basePath'])
+ filename_map = options['filenameMap']
+
+ for res in resolutions[cat]:
+ filename = make_filename(filename_map[cat], cat, name, color, size=32)
+ target_path = make_target_path(base_path, proj_path, res, filename)
+ url = globals()['make_' + cat + '_icon_url'](res, name, color)
+ download_url(url, target_path)
+
+
+def print_usage():
+ print("Usage:")
+ print("Material : ./copy [size]")
+ print("Classic & FA: ./copy ")
+ print("")
+
+
+def main():
+ import json
+
+ if len(sys.argv) < 5:
+ print_usage()
+ return
+
+ option_filename = 'options.json'
+ if not os.path.exists(option_filename):
+ option_filename = 'options.templ.json'
+ print("WARNING: using the template options file")
+ print("You should create your own options.json")
+
+ with open(option_filename, 'r') as fd:
+ options = json.load(fd)
+
+ proj_path = sys.argv[1]
+ cat = sys.argv[2]
+ color = sys.argv[3]
+ name = sys.argv[4]
+
+ if cat == 'classic' or cat == 'fa':
+ do_classic_or_fa(options, proj_path, cat, name, color)
+ else:
+ size = sys.argv[5] if len(sys.argv) >= 6 else 0
+ size = int(size) or 24
+ do_material(options, proj_path, cat, name, color, size)
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except AppError as e:
+ print(e.message)
+
+
diff --git a/Graphics/android-icon-copier/options.templ.json b/Graphics/android-icon-copier/options.templ.json
new file mode 100644
index 000000000..319d90f42
--- /dev/null
+++ b/Graphics/android-icon-copier/options.templ.json
@@ -0,0 +1,8 @@
+{
+ "basePath": "~/Documents",
+ "filenameMap": {
+ "classic": "ic_action_{name}{bgSuffix}.png",
+ "fa": "ic_action_fa_{name}{bgSuffix}.png",
+ "material": "ic_{name}_{color}_{size}dp.png"
+ }
+}
diff --git a/Resources/graphics/All_Icons.svg b/Graphics/drawables/All_Icons.svg
similarity index 100%
rename from Resources/graphics/All_Icons.svg
rename to Graphics/drawables/All_Icons.svg
diff --git a/Resources/graphics/create_key_robot.svg b/Graphics/drawables/create_key_robot.svg
similarity index 100%
rename from Resources/graphics/create_key_robot.svg
rename to Graphics/drawables/create_key_robot.svg
diff --git a/Resources/graphics/first_time_1.png b/Graphics/drawables/first_time_1.png
similarity index 100%
rename from Resources/graphics/first_time_1.png
rename to Graphics/drawables/first_time_1.png
diff --git a/Resources/graphics/first_time_1.svg b/Graphics/drawables/first_time_1.svg
similarity index 100%
rename from Resources/graphics/first_time_1.svg
rename to Graphics/drawables/first_time_1.svg
diff --git a/Resources/graphics/function.png b/Graphics/drawables/function.png
similarity index 100%
rename from Resources/graphics/function.png
rename to Graphics/drawables/function.png
diff --git a/Resources/graphics/function.svg b/Graphics/drawables/function.svg
similarity index 100%
rename from Resources/graphics/function.svg
rename to Graphics/drawables/function.svg
diff --git a/Resources/graphics/ic_action_nfc.svg b/Graphics/drawables/ic_action_nfc.svg
similarity index 100%
rename from Resources/graphics/ic_action_nfc.svg
rename to Graphics/drawables/ic_action_nfc.svg
diff --git a/Resources/graphics/ic_action_qr_code.svg b/Graphics/drawables/ic_action_qr_code.svg
similarity index 100%
rename from Resources/graphics/ic_action_qr_code.svg
rename to Graphics/drawables/ic_action_qr_code.svg
diff --git a/Resources/graphics/ic_action_safeslinger.svg b/Graphics/drawables/ic_action_safeslinger.svg
similarity index 100%
rename from Resources/graphics/ic_action_safeslinger.svg
rename to Graphics/drawables/ic_action_safeslinger.svg
diff --git a/Resources/graphics/ic_action_search_cloud.svg b/Graphics/drawables/ic_action_search_cloud.svg
similarity index 100%
rename from Resources/graphics/ic_action_search_cloud.svg
rename to Graphics/drawables/ic_action_search_cloud.svg
diff --git a/Resources/graphics/ic_launcher_old.svg b/Graphics/drawables/ic_launcher_old.svg
similarity index 100%
rename from Resources/graphics/ic_launcher_old.svg
rename to Graphics/drawables/ic_launcher_old.svg
diff --git a/Resources/graphics/icon_sizes.txt b/Graphics/drawables/icon_sizes.txt
similarity index 100%
rename from Resources/graphics/icon_sizes.txt
rename to Graphics/drawables/icon_sizes.txt
diff --git a/Resources/graphics/key_flag_authenticate.svg b/Graphics/drawables/key_flag_authenticate.svg
similarity index 100%
rename from Resources/graphics/key_flag_authenticate.svg
rename to Graphics/drawables/key_flag_authenticate.svg
diff --git a/Resources/graphics/key_flag_certify.svg b/Graphics/drawables/key_flag_certify.svg
similarity index 100%
rename from Resources/graphics/key_flag_certify.svg
rename to Graphics/drawables/key_flag_certify.svg
diff --git a/Resources/graphics/key_flag_encrypt.svg b/Graphics/drawables/key_flag_encrypt.svg
similarity index 100%
rename from Resources/graphics/key_flag_encrypt.svg
rename to Graphics/drawables/key_flag_encrypt.svg
diff --git a/Resources/graphics/key_flag_sign.svg b/Graphics/drawables/key_flag_sign.svg
similarity index 100%
rename from Resources/graphics/key_flag_sign.svg
rename to Graphics/drawables/key_flag_sign.svg
diff --git a/Resources/graphics/originals/gnupg-infographic/README b/Graphics/drawables/originals/gnupg-infographic/README
similarity index 100%
rename from Resources/graphics/originals/gnupg-infographic/README
rename to Graphics/drawables/originals/gnupg-infographic/README
diff --git a/Resources/graphics/originals/gnupg-infographic/gnupg-infographic.png b/Graphics/drawables/originals/gnupg-infographic/gnupg-infographic.png
similarity index 100%
rename from Resources/graphics/originals/gnupg-infographic/gnupg-infographic.png
rename to Graphics/drawables/originals/gnupg-infographic/gnupg-infographic.png
diff --git a/Resources/graphics/originals/gnupg-infographic/gnupg-infographic.svg b/Graphics/drawables/originals/gnupg-infographic/gnupg-infographic.svg
similarity index 100%
rename from Resources/graphics/originals/gnupg-infographic/gnupg-infographic.svg
rename to Graphics/drawables/originals/gnupg-infographic/gnupg-infographic.svg
diff --git a/Resources/graphics/originals/ic_action_nfc/NFC.png b/Graphics/drawables/originals/ic_action_nfc/NFC.png
similarity index 100%
rename from Resources/graphics/originals/ic_action_nfc/NFC.png
rename to Graphics/drawables/originals/ic_action_nfc/NFC.png
diff --git a/Resources/graphics/originals/ic_action_qr_code/ic_menu_qr_code.svg b/Graphics/drawables/originals/ic_action_qr_code/ic_menu_qr_code.svg
similarity index 100%
rename from Resources/graphics/originals/ic_action_qr_code/ic_menu_qr_code.svg
rename to Graphics/drawables/originals/ic_action_qr_code/ic_menu_qr_code.svg
diff --git a/Resources/graphics/originals/ic_launcher/AUTHORS b/Graphics/drawables/originals/ic_launcher/AUTHORS
similarity index 100%
rename from Resources/graphics/originals/ic_launcher/AUTHORS
rename to Graphics/drawables/originals/ic_launcher/AUTHORS
diff --git a/Resources/graphics/originals/ic_launcher/COPYING b/Graphics/drawables/originals/ic_launcher/COPYING
similarity index 100%
rename from Resources/graphics/originals/ic_launcher/COPYING
rename to Graphics/drawables/originals/ic_launcher/COPYING
diff --git a/Resources/graphics/originals/ic_launcher/kgpg_key2_kopete.svgz b/Graphics/drawables/originals/ic_launcher/kgpg_key2_kopete.svgz
similarity index 100%
rename from Resources/graphics/originals/ic_launcher/kgpg_key2_kopete.svgz
rename to Graphics/drawables/originals/ic_launcher/kgpg_key2_kopete.svgz
diff --git a/Resources/graphics/originals/modernpgp-icons/README.md b/Graphics/drawables/originals/modernpgp-icons/README.md
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/README.md
rename to Graphics/drawables/originals/modernpgp-icons/README.md
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-closed.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-closed.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-closed.svg b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-closed.svg
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-closed@200.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-closed@200.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-closed@300.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-closed@300.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-closed@512x.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-closed@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-closed@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-error.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-error.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-error.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-error.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-error.svg b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-error.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-error.svg
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-error.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-error@200.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-error@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-error@200.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-error@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-error@300.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-error@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-error@300.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-error@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-error@512x.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-error@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-error@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-error@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-open.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-open.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-open.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-open.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-open.svg b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-open.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-open.svg
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-open.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-open@200.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-open@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-open@200.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-open@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-open@300.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-open@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-open@300.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-open@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/encryption/lock-open@512x.png b/Graphics/drawables/originals/modernpgp-icons/encryption/lock-open@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/encryption/lock-open@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/encryption/lock-open@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint.svg b/Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint.svg
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint@200.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint@200.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint@300.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint@300.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint@512x.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-fingerprint@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-fingerprint@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-key.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-key.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-key.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-key.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-key.svg b/Graphics/drawables/originals/modernpgp-icons/keys/icon-key.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-key.svg
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-key.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-key@200.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-key@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-key@200.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-key@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-key@300.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-key@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-key@300.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-key@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/keys/icon-key@512x.png b/Graphics/drawables/originals/modernpgp-icons/keys/icon-key@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/keys/icon-key@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/keys/icon-key@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired-cutout@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired-cutout@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-expired@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-expired@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-expired@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid-cutout@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid-cutout@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-invalid@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-invalid@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked-cutout@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked-cutout@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-revoked@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-revoked@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown-cutout@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown-cutout@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unknown@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unknown@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified-cutout@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified-cutout@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-unverified@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-unverified@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified-cutout@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified-cutout@512x.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified.svg b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified.svg
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified.svg
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified.svg
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified@200.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified@200.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified@200.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified@200.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified@300.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified@300.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified@300.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified@300.png
diff --git a/Resources/graphics/originals/modernpgp-icons/signatures/signature-verified@512x.png b/Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified@512x.png
similarity index 100%
rename from Resources/graphics/originals/modernpgp-icons/signatures/signature-verified@512x.png
rename to Graphics/drawables/originals/modernpgp-icons/signatures/signature-verified@512x.png
diff --git a/Resources/graphics/originals/tango or oxygen/1270234450.svg b/Graphics/drawables/originals/tango or oxygen/1270234450.svg
similarity index 100%
rename from Resources/graphics/originals/tango or oxygen/1270234450.svg
rename to Graphics/drawables/originals/tango or oxygen/1270234450.svg
diff --git a/Resources/graphics/originals/tango or oxygen/application-pgp-signature.svg b/Graphics/drawables/originals/tango or oxygen/application-pgp-signature.svg
similarity index 100%
rename from Resources/graphics/originals/tango or oxygen/application-pgp-signature.svg
rename to Graphics/drawables/originals/tango or oxygen/application-pgp-signature.svg
diff --git a/Resources/graphics/originals/tango or oxygen/application-pkcs7-signature.svg b/Graphics/drawables/originals/tango or oxygen/application-pkcs7-signature.svg
similarity index 100%
rename from Resources/graphics/originals/tango or oxygen/application-pkcs7-signature.svg
rename to Graphics/drawables/originals/tango or oxygen/application-pkcs7-signature.svg
diff --git a/Resources/graphics/originals/tango or oxygen/osa_id_card.svg b/Graphics/drawables/originals/tango or oxygen/osa_id_card.svg
similarity index 100%
rename from Resources/graphics/originals/tango or oxygen/osa_id_card.svg
rename to Graphics/drawables/originals/tango or oxygen/osa_id_card.svg
diff --git a/Resources/graphics/originals/tango or oxygen/osa_padlock.svg b/Graphics/drawables/originals/tango or oxygen/osa_padlock.svg
similarity index 100%
rename from Resources/graphics/originals/tango or oxygen/osa_padlock.svg
rename to Graphics/drawables/originals/tango or oxygen/osa_padlock.svg
diff --git a/Resources/graphics/originals/tango or oxygen/tango-style-pen.svg b/Graphics/drawables/originals/tango or oxygen/tango-style-pen.svg
similarity index 100%
rename from Resources/graphics/originals/tango or oxygen/tango-style-pen.svg
rename to Graphics/drawables/originals/tango or oxygen/tango-style-pen.svg
diff --git a/Resources/graphics/status_lock_closed.svg b/Graphics/drawables/status_lock_closed.svg
similarity index 100%
rename from Resources/graphics/status_lock_closed.svg
rename to Graphics/drawables/status_lock_closed.svg
diff --git a/Resources/graphics/status_lock_error.svg b/Graphics/drawables/status_lock_error.svg
similarity index 100%
rename from Resources/graphics/status_lock_error.svg
rename to Graphics/drawables/status_lock_error.svg
diff --git a/Resources/graphics/status_lock_open.svg b/Graphics/drawables/status_lock_open.svg
similarity index 100%
rename from Resources/graphics/status_lock_open.svg
rename to Graphics/drawables/status_lock_open.svg
diff --git a/Resources/graphics/status_signature_expired.svg b/Graphics/drawables/status_signature_expired.svg
similarity index 100%
rename from Resources/graphics/status_signature_expired.svg
rename to Graphics/drawables/status_signature_expired.svg
diff --git a/Resources/graphics/status_signature_expired_cutout.svg b/Graphics/drawables/status_signature_expired_cutout.svg
similarity index 100%
rename from Resources/graphics/status_signature_expired_cutout.svg
rename to Graphics/drawables/status_signature_expired_cutout.svg
diff --git a/Resources/graphics/status_signature_invalid.svg b/Graphics/drawables/status_signature_invalid.svg
similarity index 100%
rename from Resources/graphics/status_signature_invalid.svg
rename to Graphics/drawables/status_signature_invalid.svg
diff --git a/Resources/graphics/status_signature_invalid_cutout.svg b/Graphics/drawables/status_signature_invalid_cutout.svg
similarity index 100%
rename from Resources/graphics/status_signature_invalid_cutout.svg
rename to Graphics/drawables/status_signature_invalid_cutout.svg
diff --git a/Resources/graphics/status_signature_revoked.svg b/Graphics/drawables/status_signature_revoked.svg
similarity index 100%
rename from Resources/graphics/status_signature_revoked.svg
rename to Graphics/drawables/status_signature_revoked.svg
diff --git a/Resources/graphics/status_signature_revoked_cutout.svg b/Graphics/drawables/status_signature_revoked_cutout.svg
similarity index 100%
rename from Resources/graphics/status_signature_revoked_cutout.svg
rename to Graphics/drawables/status_signature_revoked_cutout.svg
diff --git a/Resources/graphics/status_signature_unknown.svg b/Graphics/drawables/status_signature_unknown.svg
similarity index 100%
rename from Resources/graphics/status_signature_unknown.svg
rename to Graphics/drawables/status_signature_unknown.svg
diff --git a/Resources/graphics/status_signature_unknown_cutout.svg b/Graphics/drawables/status_signature_unknown_cutout.svg
similarity index 100%
rename from Resources/graphics/status_signature_unknown_cutout.svg
rename to Graphics/drawables/status_signature_unknown_cutout.svg
diff --git a/Resources/graphics/status_signature_unverified.svg b/Graphics/drawables/status_signature_unverified.svg
similarity index 100%
rename from Resources/graphics/status_signature_unverified.svg
rename to Graphics/drawables/status_signature_unverified.svg
diff --git a/Resources/graphics/status_signature_unverified_cutout.svg b/Graphics/drawables/status_signature_unverified_cutout.svg
similarity index 100%
rename from Resources/graphics/status_signature_unverified_cutout.svg
rename to Graphics/drawables/status_signature_unverified_cutout.svg
diff --git a/Resources/graphics/status_signature_verified.svg b/Graphics/drawables/status_signature_verified.svg
similarity index 100%
rename from Resources/graphics/status_signature_verified.svg
rename to Graphics/drawables/status_signature_verified.svg
diff --git a/Resources/graphics/status_signature_verified_cutout.svg b/Graphics/drawables/status_signature_verified_cutout.svg
similarity index 100%
rename from Resources/graphics/status_signature_verified_cutout.svg
rename to Graphics/drawables/status_signature_verified_cutout.svg
diff --git a/Graphics/get-material-icons.sh b/Graphics/get-material-icons.sh
new file mode 100755
index 000000000..0d7d688f2
--- /dev/null
+++ b/Graphics/get-material-icons.sh
@@ -0,0 +1,16 @@
+# https://google.github.io/material-design-icons/
+cd ./android-icon-copier/
+python copy OpenKeychain action white search 24
+python copy OpenKeychain navigation white arrow_back 24
+python copy OpenKeychain navigation white close 24
+python copy OpenKeychain navigation white check 24
+python copy OpenKeychain navigation black expand_less 24
+python copy OpenKeychain navigation black expand_more 24
+python copy OpenKeychain navigation white refresh 24
+
+# navigation drawer sections
+python copy OpenKeychain communication black vpn_key 24
+python copy OpenKeychain action black lock_open 24
+python copy OpenKeychain action black lock_outline 24
+python copy OpenKeychain navigation black apps 24
+python copy OpenKeychain action black settings 24
\ No newline at end of file
diff --git a/Resources/new icon/Feature Graphic.psd b/Graphics/material-launcher/Feature Graphic.psd
similarity index 100%
rename from Resources/new icon/Feature Graphic.psd
rename to Graphics/material-launcher/Feature Graphic.psd
diff --git a/Resources/new icon/Feature-Graphic.png b/Graphics/material-launcher/Feature-Graphic.png
similarity index 100%
rename from Resources/new icon/Feature-Graphic.png
rename to Graphics/material-launcher/Feature-Graphic.png
diff --git a/Resources/new icon/preview.psd b/Graphics/material-launcher/preview.psd
similarity index 100%
rename from Resources/new icon/preview.psd
rename to Graphics/material-launcher/preview.psd
diff --git a/Resources/new icon/preview1.png b/Graphics/material-launcher/preview1.png
similarity index 100%
rename from Resources/new icon/preview1.png
rename to Graphics/material-launcher/preview1.png
diff --git a/Resources/new icon/preview2.png b/Graphics/material-launcher/preview2.png
similarity index 100%
rename from Resources/new icon/preview2.png
rename to Graphics/material-launcher/preview2.png
diff --git a/Resources/new icon/preview3.png b/Graphics/material-launcher/preview3.png
similarity index 100%
rename from Resources/new icon/preview3.png
rename to Graphics/material-launcher/preview3.png
diff --git a/Resources/new icon/ready-launcher-icons/144/vector-src-blue.png b/Graphics/material-launcher/ready-launcher-icons/144/vector-src-blue.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/144/vector-src-blue.png
rename to Graphics/material-launcher/ready-launcher-icons/144/vector-src-blue.png
diff --git a/Resources/new icon/ready-launcher-icons/144/vector-src-purple.png b/Graphics/material-launcher/ready-launcher-icons/144/vector-src-purple.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/144/vector-src-purple.png
rename to Graphics/material-launcher/ready-launcher-icons/144/vector-src-purple.png
diff --git a/Resources/new icon/ready-launcher-icons/144/vector-src.png b/Graphics/material-launcher/ready-launcher-icons/144/vector-src.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/144/vector-src.png
rename to Graphics/material-launcher/ready-launcher-icons/144/vector-src.png
diff --git a/Resources/new icon/ready-launcher-icons/192/vector-src-blue.png b/Graphics/material-launcher/ready-launcher-icons/192/vector-src-blue.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/192/vector-src-blue.png
rename to Graphics/material-launcher/ready-launcher-icons/192/vector-src-blue.png
diff --git a/Resources/new icon/ready-launcher-icons/192/vector-src-purple.png b/Graphics/material-launcher/ready-launcher-icons/192/vector-src-purple.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/192/vector-src-purple.png
rename to Graphics/material-launcher/ready-launcher-icons/192/vector-src-purple.png
diff --git a/Resources/new icon/ready-launcher-icons/192/vector-src.png b/Graphics/material-launcher/ready-launcher-icons/192/vector-src.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/192/vector-src.png
rename to Graphics/material-launcher/ready-launcher-icons/192/vector-src.png
diff --git a/Resources/new icon/ready-launcher-icons/48/vector-src-blue.png b/Graphics/material-launcher/ready-launcher-icons/48/vector-src-blue.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/48/vector-src-blue.png
rename to Graphics/material-launcher/ready-launcher-icons/48/vector-src-blue.png
diff --git a/Resources/new icon/ready-launcher-icons/48/vector-src-purple.png b/Graphics/material-launcher/ready-launcher-icons/48/vector-src-purple.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/48/vector-src-purple.png
rename to Graphics/material-launcher/ready-launcher-icons/48/vector-src-purple.png
diff --git a/Resources/new icon/ready-launcher-icons/48/vector-src.png b/Graphics/material-launcher/ready-launcher-icons/48/vector-src.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/48/vector-src.png
rename to Graphics/material-launcher/ready-launcher-icons/48/vector-src.png
diff --git a/Resources/new icon/ready-launcher-icons/512/vector-src-blue.png b/Graphics/material-launcher/ready-launcher-icons/512/vector-src-blue.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/512/vector-src-blue.png
rename to Graphics/material-launcher/ready-launcher-icons/512/vector-src-blue.png
diff --git a/Resources/new icon/ready-launcher-icons/512/vector-src-purple.png b/Graphics/material-launcher/ready-launcher-icons/512/vector-src-purple.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/512/vector-src-purple.png
rename to Graphics/material-launcher/ready-launcher-icons/512/vector-src-purple.png
diff --git a/Resources/new icon/ready-launcher-icons/512/vector-src.png b/Graphics/material-launcher/ready-launcher-icons/512/vector-src.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/512/vector-src.png
rename to Graphics/material-launcher/ready-launcher-icons/512/vector-src.png
diff --git a/Resources/new icon/ready-launcher-icons/72/vector-src-blue.png b/Graphics/material-launcher/ready-launcher-icons/72/vector-src-blue.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/72/vector-src-blue.png
rename to Graphics/material-launcher/ready-launcher-icons/72/vector-src-blue.png
diff --git a/Resources/new icon/ready-launcher-icons/72/vector-src-purple.png b/Graphics/material-launcher/ready-launcher-icons/72/vector-src-purple.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/72/vector-src-purple.png
rename to Graphics/material-launcher/ready-launcher-icons/72/vector-src-purple.png
diff --git a/Resources/new icon/ready-launcher-icons/72/vector-src.png b/Graphics/material-launcher/ready-launcher-icons/72/vector-src.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/72/vector-src.png
rename to Graphics/material-launcher/ready-launcher-icons/72/vector-src.png
diff --git a/Resources/new icon/ready-launcher-icons/96/vector-src-blue.png b/Graphics/material-launcher/ready-launcher-icons/96/vector-src-blue.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/96/vector-src-blue.png
rename to Graphics/material-launcher/ready-launcher-icons/96/vector-src-blue.png
diff --git a/Resources/new icon/ready-launcher-icons/96/vector-src-purple.png b/Graphics/material-launcher/ready-launcher-icons/96/vector-src-purple.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/96/vector-src-purple.png
rename to Graphics/material-launcher/ready-launcher-icons/96/vector-src-purple.png
diff --git a/Resources/new icon/ready-launcher-icons/96/vector-src.png b/Graphics/material-launcher/ready-launcher-icons/96/vector-src.png
similarity index 100%
rename from Resources/new icon/ready-launcher-icons/96/vector-src.png
rename to Graphics/material-launcher/ready-launcher-icons/96/vector-src.png
diff --git a/Resources/new icon/vector-src blue.ai b/Graphics/material-launcher/vector-src blue.ai
similarity index 100%
rename from Resources/new icon/vector-src blue.ai
rename to Graphics/material-launcher/vector-src blue.ai
diff --git a/Resources/new icon/vector-src purple.ai b/Graphics/material-launcher/vector-src purple.ai
similarity index 100%
rename from Resources/new icon/vector-src purple.ai
rename to Graphics/material-launcher/vector-src purple.ai
diff --git a/Resources/new icon/vector-src purple.psd b/Graphics/material-launcher/vector-src purple.psd
similarity index 100%
rename from Resources/new icon/vector-src purple.psd
rename to Graphics/material-launcher/vector-src purple.psd
diff --git a/Resources/new icon/vector-src.ai b/Graphics/material-launcher/vector-src.ai
similarity index 100%
rename from Resources/new icon/vector-src.ai
rename to Graphics/material-launcher/vector-src.ai
diff --git a/Resources/new icon/vector-src.psd b/Graphics/material-launcher/vector-src.psd
similarity index 100%
rename from Resources/new icon/vector-src.psd
rename to Graphics/material-launcher/vector-src.psd
diff --git a/Graphics/material-launcher/vector-src.svg b/Graphics/material-launcher/vector-src.svg
new file mode 100644
index 000000000..a4d255d48
--- /dev/null
+++ b/Graphics/material-launcher/vector-src.svg
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/new icon/vector.psd b/Graphics/material-launcher/vector.psd
similarity index 100%
rename from Resources/new icon/vector.psd
rename to Graphics/material-launcher/vector.psd
diff --git a/Graphics/update-drawables.sh b/Graphics/update-drawables.sh
new file mode 100755
index 000000000..eef97c7c3
--- /dev/null
+++ b/Graphics/update-drawables.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+APP_DIR=../OpenKeychain/src/main
+MDPI_DIR=$APP_DIR/res/drawable-mdpi
+HDPI_DIR=$APP_DIR/res/drawable-hdpi
+XDPI_DIR=$APP_DIR/res/drawable-xhdpi
+XXDPI_DIR=$APP_DIR/res/drawable-xxhdpi
+XXXDPI_DIR=$APP_DIR/res/drawable-xxxhdpi
+PLAY_DIR=./drawables/
+SRC_DIR=./drawables/
+
+
+
+# Launcher Icon:
+# -----------------------
+# mdpi: 48x48
+# hdpi: 72x72
+# xhdpi: 96x96
+# xxhdpi: 144x144.
+# xxxhdpi 192x192.
+# google play: 512x512
+
+# Adobe Illustrator (.ai) exports by Tha Phlash are way better than the Inkscape exports (.svg)
+
+#NAME="ic_launcher"
+
+#inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" $NAME.svg
+#inkscape -w 72 -h 72 -e "$HDPI_DIR/$NAME.png" $NAME.svg
+#inkscape -w 96 -h 96 -e "$XDPI_DIR/$NAME.png" $NAME.svg
+#inkscape -w 144 -h 144 -e "$XXDPI_DIR/$NAME.png" $NAME.svg
+#inkscape -w 192 -h 192 -e "$XXXDPI_DIR/$NAME.png" $NAME.svg
+#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
+
+# Actionbar Icons
+# -----------------------
+# mdpi: 32x32
+# hdpi: 48x48
+# xhdpi: 64x64
+# xxhdpi: 96x96
+
+for NAME in "ic_action_nfc" "ic_action_qr_code" "ic_action_safeslinger" "ic_action_search_cloud"
+do
+echo $NAME
+inkscape -w 32 -h 32 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 48 -h 48 -e "$HDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 64 -h 64 -e "$XDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 96 -h 96 -e "$XXDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+done
+
+for NAME in "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_expired" "status_signature_invalid_cutout" "status_signature_invalid" "status_signature_revoked_cutout" "status_signature_revoked" "status_signature_unknown_cutout" "status_signature_unknown" "status_signature_unverified_cutout" "status_signature_unverified" "status_signature_verified_cutout" "status_signature_verified" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign"
+do
+echo $NAME
+inkscape -w 24 -h 24 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 32 -h 32 -e "$HDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 48 -h 48 -e "$XDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 64 -h 64 -e "$XXDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+done
+
+for NAME in "create_key_robot"
+do
+echo $NAME
+inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 64 -h 64 -e "$HDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 96 -h 96 -e "$XDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+inkscape -w 128 -h 128 -e "$XXDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
+done
\ No newline at end of file
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java
new file mode 100644
index 000000000..40ade064b
--- /dev/null
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2014 Vincent Breitmoser
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.operations;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowLog;
+import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
+import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
+import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+import org.sufficientlysecure.keychain.util.TestingUtils;
+
+import java.io.PrintStream;
+import java.security.Security;
+import java.util.Iterator;
+
+@RunWith(RobolectricTestRunner.class)
+@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
+public class PromoteKeyOperationTest {
+
+ static UncachedKeyRing mStaticRing;
+ static String mKeyPhrase1 = TestingUtils.genPassphrase(true);
+
+ static PrintStream oldShadowStream;
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ Security.insertProviderAt(new BouncyCastleProvider(), 1);
+ oldShadowStream = ShadowLog.stream;
+ // ShadowLog.stream = System.out;
+
+ PgpKeyOperation op = new PgpKeyOperation(null);
+
+ {
+ SaveKeyringParcel parcel = new SaveKeyringParcel();
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
+ parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
+ Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
+ parcel.mAddUserIds.add("derp");
+ parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1);
+
+ PgpEditKeyResult result = op.createSecretKeyRing(parcel);
+ Assert.assertTrue("initial test key creation must succeed", result.success());
+ Assert.assertNotNull("initial test key creation must succeed", result.getRing());
+
+ mStaticRing = result.getRing();
+ }
+
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ ProviderHelper providerHelper = new ProviderHelper(Robolectric.application);
+
+ // don't log verbosely here, we're not here to test imports
+ ShadowLog.stream = oldShadowStream;
+
+ providerHelper.savePublicKeyRing(mStaticRing.extractPublicKeyRing(), new ProgressScaler());
+
+ // ok NOW log verbosely!
+ ShadowLog.stream = System.out;
+ }
+
+ @Test
+ public void testPromote() throws Exception {
+ PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application,
+ new ProviderHelper(Robolectric.application), null, null);
+
+ PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId());
+
+ Assert.assertTrue("promotion must succeed", result.success());
+
+ {
+ CachedPublicKeyRing ring = new ProviderHelper(Robolectric.application)
+ .getCachedPublicKeyRing(mStaticRing.getMasterKeyId());
+ Assert.assertTrue("key must have a secret now", ring.hasAnySecret());
+
+ Iterator it = mStaticRing.getPublicKeys();
+ while (it.hasNext()) {
+ long keyId = it.next().getKeyId();
+ Assert.assertEquals("all subkeys must be divert-to-card",
+ SecretKeyType.GNU_DUMMY, ring.getSecretKeyType(keyId));
+ }
+ }
+
+ // second attempt should fail
+ result = op.execute(mStaticRing.getMasterKeyId());
+ Assert.assertFalse("promotion of secret key must fail", result.success());
+
+ }
+
+}
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
index 52115a76d..0288d2937 100644
--- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
@@ -34,6 +34,8 @@ import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.SecretKeyPacket;
import org.spongycastle.bcpg.SecretSubkeyPacket;
import org.spongycastle.bcpg.SignaturePacket;
+import org.spongycastle.bcpg.UserAttributePacket;
+import org.spongycastle.bcpg.UserAttributeSubpacket;
import org.spongycastle.bcpg.UserIDPacket;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
@@ -657,7 +659,8 @@ public class PgpKeyOperationTest {
{ // re-add second subkey
parcel.reset();
- parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, null));
+ // re-certify the revoked subkey
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true));
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
@@ -699,7 +702,7 @@ public class PgpKeyOperationTest {
public void testSubkeyStrip() throws Exception {
long keyId = KeyringTestingHelper.getSubkeyId(ring, 1);
- parcel.mStripSubKeys.add(keyId);
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
applyModificationWithChecks(parcel, ring, onlyA, onlyB);
Assert.assertEquals("one extra packet in original", 1, onlyA.size());
@@ -725,7 +728,7 @@ public class PgpKeyOperationTest {
public void testMasterStrip() throws Exception {
long keyId = ring.getMasterKeyId();
- parcel.mStripSubKeys.add(keyId);
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
applyModificationWithChecks(parcel, ring, onlyA, onlyB);
Assert.assertEquals("one extra packet in original", 1, onlyA.size());
@@ -744,6 +747,44 @@ public class PgpKeyOperationTest {
Assert.assertEquals("new packet secret key data should have length zero",
0, ((SecretKeyPacket) p).getSecretKeyData().length);
Assert.assertNull("new packet should have no iv data", ((SecretKeyPacket) p).getIV());
+ }
+
+ @Test
+ public void testRestrictedStrip() throws Exception {
+
+ long keyId = KeyringTestingHelper.getSubkeyId(ring, 1);
+ UncachedKeyRing modified;
+
+ { // we should be able to change the stripped/divert status of subkeys without passphrase
+ parcel.reset();
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
+ modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, null);
+ Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
+ Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertEquals("new packet should have GNU_DUMMY S2K type",
+ S2K.GNU_DUMMY_S2K, ((SecretKeyPacket) p).getS2K().getType());
+ Assert.assertEquals("new packet should have GNU_DUMMY protection mode stripped",
+ S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY, ((SecretKeyPacket) p).getS2K().getProtectionMode());
+ }
+
+ { // and again, changing to divert-to-card
+ parcel.reset();
+ byte[] serial = new byte[] {
+ 0x6a, 0x6f, 0x6c, 0x6f, 0x73, 0x77, 0x61, 0x67,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ parcel.mChangeSubKeys.add(new SubkeyChange(keyId, false, serial));
+ modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, null);
+ Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
+ Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertEquals("new packet should have GNU_DUMMY S2K type",
+ S2K.GNU_DUMMY_S2K, ((SecretKeyPacket) p).getS2K().getType());
+ Assert.assertEquals("new packet should have GNU_DUMMY protection mode divert-to-card",
+ S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD, ((SecretKeyPacket) p).getS2K().getProtectionMode());
+ Assert.assertArrayEquals("new packet should have correct serial number as iv",
+ serial, ((SecretKeyPacket) p).getIV());
+
+ }
}
@@ -864,6 +905,70 @@ public class PgpKeyOperationTest {
}
+ @Test
+ public void testUserAttributeAdd() throws Exception {
+
+ {
+ parcel.mAddUserAttribute.add(WrappedUserAttribute.fromData(new byte[0]));
+ assertModifyFailure("adding an empty user attribute should fail", ring, parcel,
+ LogType.MSG_MF_UAT_ERROR_EMPTY);
+ }
+
+ parcel.reset();
+
+ Random r = new Random();
+ int type = r.nextInt(110)+1;
+ byte[] data = new byte[r.nextInt(2000)];
+ new Random().nextBytes(data);
+
+ WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(type, data);
+ parcel.mAddUserAttribute.add(uat);
+
+ UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
+
+ Assert.assertEquals("no extra packets in original", 0, onlyA.size());
+ Assert.assertEquals("exactly two extra packets in modified", 2, onlyB.size());
+
+ Assert.assertTrue("keyring must contain added user attribute",
+ modified.getPublicKey().getUnorderedUserAttributes().contains(uat));
+
+ Packet p;
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("first new packet must be user attribute", p instanceof UserAttributePacket);
+ {
+ UserAttributeSubpacket[] subpackets = ((UserAttributePacket) p).getSubpackets();
+ Assert.assertEquals("user attribute packet must contain one subpacket",
+ 1, subpackets.length);
+ Assert.assertEquals("user attribute subpacket type must be as specified above",
+ type, subpackets[0].getType());
+ Assert.assertArrayEquals("user attribute subpacket data must be as specified above",
+ data, subpackets[0].getData());
+ }
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(1).buf)).readPacket();
+ Assert.assertTrue("second new packet must be signature", p instanceof SignaturePacket);
+ Assert.assertEquals("signature type must be positive certification",
+ PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
+
+ // make sure packets can be distinguished by timestamp
+ Thread.sleep(1000);
+
+ // applying the same modification AGAIN should not add more certifications but drop those
+ // as duplicates
+ modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, passphrase, true, false);
+
+ Assert.assertEquals("duplicate modification: one extra packet in original", 1, onlyA.size());
+ Assert.assertEquals("duplicate modification: one extra packet in modified", 1, onlyB.size());
+
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
+ Assert.assertTrue("lost packet must be signature", p instanceof SignaturePacket);
+ p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
+ Assert.assertTrue("new packet must be signature", p instanceof SignaturePacket);
+
+ }
+
+
@Test
public void testUserIdPrimary() throws Exception {
@@ -1026,6 +1131,17 @@ public class PgpKeyOperationTest {
}
+ @Test
+ public void testRestricted () throws Exception {
+
+ CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
+
+ parcel.mAddUserIds.add("discord");
+ PgpKeyOperation op = new PgpKeyOperation(null);
+ PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, null);
+ Assert.assertFalse("non-restricted operations should fail without passphrase", result.success());
+ }
+
private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
UncachedKeyRing ring,
ArrayList onlyA,
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java
index 721d1a51d..f9e0d52c3 100644
--- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringCanonicalizeTest.java
@@ -104,6 +104,12 @@ public class UncachedKeyringCanonicalizeTest {
parcel.mAddUserIds.add("twi");
parcel.mAddUserIds.add("pink");
+ {
+ WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(100,
+ "sunshine, sunshine, ladybugs awake~".getBytes());
+ parcel.mAddUserAttribute.add(uat);
+ }
+
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel("");
PgpKeyOperation op = new PgpKeyOperation(null);
@@ -116,7 +122,7 @@ public class UncachedKeyringCanonicalizeTest {
staticRing = staticRing.canonicalize(new OperationLog(), 0).getUncachedKeyRing();
// just for later reference
- totalPackets = 9;
+ totalPackets = 11;
// we sleep here for a second, to make sure all new certificates have different timestamps
Thread.sleep(1000);
@@ -150,8 +156,8 @@ public class UncachedKeyringCanonicalizeTest {
Assert.assertEquals("packet #4 should be signature",
PacketTags.SIGNATURE, it.next().tag);
- Assert.assertEquals("packet #5 should be secret subkey",
- PacketTags.SECRET_SUBKEY, it.next().tag);
+ Assert.assertEquals("packet #5 should be user id",
+ PacketTags.USER_ATTRIBUTE, it.next().tag);
Assert.assertEquals("packet #6 should be signature",
PacketTags.SIGNATURE, it.next().tag);
@@ -160,7 +166,12 @@ public class UncachedKeyringCanonicalizeTest {
Assert.assertEquals("packet #8 should be signature",
PacketTags.SIGNATURE, it.next().tag);
- Assert.assertFalse("exactly 9 packets total", it.hasNext());
+ Assert.assertEquals("packet #9 should be secret subkey",
+ PacketTags.SECRET_SUBKEY, it.next().tag);
+ Assert.assertEquals("packet #10 should be signature",
+ PacketTags.SIGNATURE, it.next().tag);
+
+ Assert.assertFalse("exactly 11 packets total", it.hasNext());
Assert.assertArrayEquals("created keyring should be constant through canonicalization",
ring.getEncoded(), ring.canonicalize(log, 0).getEncoded());
@@ -380,7 +391,7 @@ public class UncachedKeyringCanonicalizeTest {
@Test public void testSubkeyDestroy() throws Exception {
// signature for second key (first subkey)
- UncachedKeyRing modified = KeyringTestingHelper.removePacket(ring, 6);
+ UncachedKeyRing modified = KeyringTestingHelper.removePacket(ring, 8);
// canonicalization should fail, because there are no valid uids left
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
@@ -424,7 +435,7 @@ public class UncachedKeyringCanonicalizeTest {
secretKey.getPublicKey(), pKey.getPublicKey());
// inject in the right position
- UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 6);
+ UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 8);
// canonicalize, and check if we lose the bad signature
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
@@ -449,7 +460,7 @@ public class UncachedKeyringCanonicalizeTest {
secretKey.getPublicKey(), pKey.getPublicKey());
// inject in the right position
- UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 6);
+ UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 8);
// canonicalize, and check if we lose the bad signature
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
@@ -481,10 +492,10 @@ public class UncachedKeyringCanonicalizeTest {
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
secretKey.getPublicKey(), pKey.getPublicKey());
- UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig1.getEncoded(), 8);
- modified = KeyringTestingHelper.injectPacket(modified, sig2.getEncoded(), 9);
- modified = KeyringTestingHelper.injectPacket(modified, sig1.getEncoded(), 10);
- modified = KeyringTestingHelper.injectPacket(modified, sig3.getEncoded(), 11);
+ UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig1.getEncoded(), 10);
+ modified = KeyringTestingHelper.injectPacket(modified, sig2.getEncoded(), 11);
+ modified = KeyringTestingHelper.injectPacket(modified, sig1.getEncoded(), 12);
+ modified = KeyringTestingHelper.injectPacket(modified, sig3.getEncoded(), 13);
// canonicalize, and check if we lose the bad signature
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
@@ -512,13 +523,13 @@ public class UncachedKeyringCanonicalizeTest {
// get subkey packets
Iterator it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
- RawPacket subKey = KeyringTestingHelper.getNth(it, 5);
+ RawPacket subKey = KeyringTestingHelper.getNth(it, 7);
RawPacket subSig = it.next();
// inject at a second position
UncachedKeyRing modified = ring;
- modified = KeyringTestingHelper.injectPacket(modified, subKey.buf, 7);
- modified = KeyringTestingHelper.injectPacket(modified, subSig.buf, 8);
+ modified = KeyringTestingHelper.injectPacket(modified, subKey.buf, 9);
+ modified = KeyringTestingHelper.injectPacket(modified, subSig.buf, 10);
// canonicalize, and check if we lose the bad signature
OperationLog log = new OperationLog();
@@ -557,7 +568,7 @@ public class UncachedKeyringCanonicalizeTest {
sKey = new PGPSecretKey(masterSecretKey.getPrivateKey(), subPubKey, sha1Calc, false, keyEncryptor);
}
- UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sKey.getEncoded(), 5);
+ UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sKey.getEncoded(), 7);
// canonicalize, and check if we lose the bad signature
OperationLog log = new OperationLog();
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java
index 7f6f480d4..ccd47d0ee 100644
--- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java
@@ -46,6 +46,7 @@ import java.io.ByteArrayInputStream;
import java.security.Security;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.Random;
/** Tests for the UncachedKeyring.merge method.
*
@@ -97,6 +98,12 @@ public class UncachedKeyringMergeTest {
parcel.mAddUserIds.add("twi");
parcel.mAddUserIds.add("pink");
+ {
+ WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(100,
+ "sunshine, sunshine, ladybugs awake~".getBytes());
+ parcel.mAddUserAttribute.add(uat);
+ }
+
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel("");
PgpKeyOperation op = new PgpKeyOperation(null);
@@ -339,6 +346,36 @@ public class UncachedKeyringMergeTest {
}
}
+ @Test
+ public void testAddedUserAttributeSignature() throws Exception {
+
+ final UncachedKeyRing modified; {
+ parcel.reset();
+
+ Random r = new Random();
+ int type = r.nextInt(110)+1;
+ byte[] data = new byte[r.nextInt(2000)];
+ new Random().nextBytes(data);
+
+ WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(type, data);
+ parcel.mAddUserAttribute.add(uat);
+
+ CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
+ ringA.getEncoded(), false, 0);
+ modified = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
+ }
+
+ {
+ UncachedKeyRing merged = ringA.merge(modified, log, 0);
+ Assert.assertNotNull("merge must succeed", merged);
+ Assert.assertFalse(
+ "merging keyring with extra user attribute into its base should yield that same keyring",
+ KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
+ );
+ }
+
+ }
+
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b)
throws Exception {
return mergeWithChecks(a, b, a);
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java
index a3c58a5c8..65395f1ab 100644
--- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringTest.java
@@ -37,6 +37,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
+import java.util.Random;
@RunWith(RobolectricTestRunner.class)
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
@@ -59,6 +60,15 @@ public class UncachedKeyringTest {
parcel.mAddUserIds.add("twi");
parcel.mAddUserIds.add("pink");
+ {
+ Random r = new Random();
+ int type = r.nextInt(110)+1;
+ byte[] data = new byte[r.nextInt(2000)];
+ new Random().nextBytes(data);
+
+ WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(type, data);
+ parcel.mAddUserAttribute.add(uat);
+ }
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel("");
PgpKeyOperation op = new PgpKeyOperation(null);
diff --git a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java
index e4a1d62ae..171ecc377 100644
--- a/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java
+++ b/OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java
@@ -106,8 +106,8 @@ public class ProviderHelperSaveTest {
UncachedKeyRing pub = readRingFromResource("/test-keys/mailvelope_07_no_key_flags.asc");
long keyId = pub.getMasterKeyId();
- Assert.assertNull("key flags should be null",
- pub.canonicalize(new OperationLog(), 0).getPublicKey().getKeyUsage());
+ Assert.assertEquals("key flags should be zero",
+ 0, (long) pub.canonicalize(new OperationLog(), 0).getPublicKey().getKeyUsage());
mProviderHelper.savePublicKeyRing(pub);
@@ -117,7 +117,8 @@ public class ProviderHelperSaveTest {
Assert.assertEquals("master key should be encryption key", keyId, pubRing.getEncryptId());
Assert.assertEquals("master key should be encryption key (cached)", keyId, cachedRing.getEncryptId());
- Assert.assertNull("canonicalized key flags should be null", pubRing.getPublicKey().getKeyUsage());
+ Assert.assertEquals("canonicalized key flags should be zero",
+ 0, (long) pubRing.getPublicKey().getKeyUsage());
Assert.assertTrue("master key should be able to certify", pubRing.getPublicKey().canCertify());
Assert.assertTrue("master key should be allowed to sign", pubRing.getPublicKey().canSign());
Assert.assertTrue("master key should be able to encrypt", pubRing.getPublicKey().canEncrypt());
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index ff3a63fd7..c55adc5cf 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -3,14 +3,13 @@ apply plugin: 'com.android.application'
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
- compile 'com.android.support:support-v4:21.0.2'
- compile 'com.android.support:appcompat-v7:21.0.2'
+ compile 'com.android.support:support-v4:21.0.3'
+ compile 'com.android.support:appcompat-v7:21.0.3'
+ compile 'com.android.support:recyclerview-v7:21.0.3'
compile project(':extern:openpgp-api-lib')
compile project(':extern:openkeychain-api-lib')
compile project(':extern:html-textview')
compile project(':extern:StickyListHeaders:library')
- compile project(':extern:zxing-qr-code')
- compile project(':extern:zxing-android-integration')
compile project(':extern:spongycastle:core')
compile project(':extern:spongycastle:pg')
compile project(':extern:spongycastle:pkix')
@@ -21,6 +20,13 @@ dependencies {
compile project(':extern:TokenAutoComplete:library')
compile project(':extern:safeslinger-exchange')
compile project(':extern:android-lockpattern:code')
+
+ // TODO: include as submodule?:
+ compile 'com.journeyapps:zxing-android-embedded:2.0.1@aar'
+ compile 'com.journeyapps:zxing-android-integration:2.0.1@aar'
+ compile 'com.google.zxing:core:3.0.1'
+ compile 'com.jpardogo.materialtabstrip:library:1.0.8'
+ compile 'it.neokree:MaterialNavigationDrawer:1.3'
}
android {
@@ -28,10 +34,15 @@ android {
buildToolsVersion '21.1.1'
defaultConfig {
- minSdkVersion 9
+ minSdkVersion 15
targetSdkVersion 21
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
/*
* To sign release build, create file gradle.properties in ~/.gradle/ with this content:
*
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 9fc79e6f1..536b66319 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -31,10 +31,7 @@
For OI Filemanager it makes no difference, gpg files can't be associated
-->
-
-
-
-
+
@@ -434,7 +431,7 @@
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
index 255ca1cde..3b281876f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
@@ -32,10 +32,10 @@ public class CloudSearch {
public static ArrayList search(final String query, Preferences.CloudSearchPrefs cloudPrefs)
throws Keyserver.CloudSearchFailureException {
- final ArrayList servers = new ArrayList();
+ final ArrayList servers = new ArrayList<>();
// it's a Vector for sync, multiple threads might report problems
- final Vector problems = new Vector();
+ final Vector problems = new Vector<>();
if (cloudPrefs.searchKeyserver) {
servers.add(new HkpKeyserver(cloudPrefs.keyserver));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
index 7f07b8162..f39c552a3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
@@ -234,7 +234,7 @@ public class HkpKeyserver extends Keyserver {
@Override
public ArrayList search(String query) throws QueryFailedException,
QueryNeedsRepairException {
- ArrayList results = new ArrayList();
+ ArrayList results = new ArrayList<>();
if (query.length() < 3) {
throw new QueryTooShortException();
@@ -305,7 +305,7 @@ public class HkpKeyserver extends Keyserver {
entry.setRevoked(matcher.group(6).contains("r"));
entry.setExpired(matcher.group(6).contains("e"));
- ArrayList userIds = new ArrayList();
+ ArrayList userIds = new ArrayList<>();
final String uidLines = matcher.group(7);
final Matcher uidMatcher = UID_LINE.matcher(uidLines);
while (uidMatcher.find()) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java
index d60d7757b..1d5ee76a4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java
@@ -89,7 +89,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
public ImportKeysListEntry createFromParcel(final Parcel source) {
ImportKeysListEntry vr = new ImportKeysListEntry();
vr.mPrimaryUserId = source.readString();
- vr.mUserIds = new ArrayList();
+ vr.mUserIds = new ArrayList<>();
source.readStringList(vr.mUserIds);
vr.mMergedUserIds = (HashMap>) source.readSerializable();
vr.mKeyId = source.readLong();
@@ -103,7 +103,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
vr.mSecretKey = source.readByte() == 1;
vr.mSelected = source.readByte() == 1;
vr.mExtraData = source.readString();
- vr.mOrigins = new ArrayList();
+ vr.mOrigins = new ArrayList<>();
source.readStringList(vr.mOrigins);
return vr;
@@ -265,8 +265,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
mSecretKey = false;
// do not select by default
mSelected = false;
- mUserIds = new ArrayList();
- mOrigins = new ArrayList();
+ mUserIds = new ArrayList<>();
+ mOrigins = new ArrayList<>();
}
/**
@@ -304,7 +304,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
}
public void updateMergedUserIds() {
- mMergedUserIds = new HashMap>();
+ mMergedUserIds = new HashMap<>();
for (String userId : mUserIds) {
String[] userIdSplit = KeyRing.splitUserId(userId);
@@ -315,7 +315,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
// email
if (userIdSplit[1] != null) {
if (!mMergedUserIds.containsKey(userIdSplit[0])) {
- HashSet emails = new HashSet();
+ HashSet emails = new HashSet<>();
emails.add(userIdSplit[1]);
mMergedUserIds.put(userIdSplit[0], emails);
} else {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
index 2363a40b3..e310e9a3f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
@@ -36,7 +36,7 @@ public class KeybaseKeyserver extends Keyserver {
@Override
public ArrayList search(String query) throws QueryFailedException,
QueryNeedsRepairException {
- ArrayList results = new ArrayList();
+ ArrayList results = new ArrayList<>();
if (query.startsWith("0x")) {
// cut off "0x" if a user is searching for a key id
@@ -81,7 +81,7 @@ public class KeybaseKeyserver extends Keyserver {
final int algorithmId = match.getAlgorithmId();
entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitStrength, null));
- ArrayList userIds = new ArrayList();
+ ArrayList userIds = new ArrayList<>();
String name = "";
if (fullName != null) {
name = fullName + " " + name;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
index c400eb813..e796bdc91 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
@@ -77,6 +77,12 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
return mCancelled != null && mCancelled.get();
}
+ protected void setPreventCancel () {
+ if (mProgressable != null) {
+ mProgressable.setPreventCancel();
+ }
+ }
+
@Override
public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException {
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index a5eb95b07..3b391814e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -78,7 +78,7 @@ public class CertifyOperation extends BaseOperation {
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
- ArrayList certifiedKeys = new ArrayList();
+ ArrayList certifiedKeys = new ArrayList<>();
log.add(LogType.MSG_CRT_CERTIFYING, 1);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
index 4d466593b..106c62688 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
@@ -11,7 +11,6 @@ import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
-import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
@@ -29,7 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* create key operations in PgpKeyOperation. It takes care of fetching
* and saving the key before and after the operation.
*
- * @see CertifyActionsParcel
+ * @see SaveKeyringParcel
*
*/
public class EditKeyOperation extends BaseOperation {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
index 6ca28142f..cd8a33c20 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
@@ -137,7 +137,7 @@ public class ImportExportOperation extends BaseOperation {
}
int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0;
- ArrayList importedMasterKeyIds = new ArrayList();
+ ArrayList importedMasterKeyIds = new ArrayList<>();
boolean cancelled = false;
int position = 0;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
new file mode 100644
index 000000000..f10d11684
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
@@ -0,0 +1,103 @@
+package org.sufficientlysecure.keychain.operations;
+
+import android.content.Context;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
+import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** An operation which promotes a public key ring to a secret one.
+ *
+ * This operation can only be applied to public key rings where no secret key
+ * is available. Doing this "promotes" the public key ring to a secret one
+ * without secret key material, using a GNU_DUMMY s2k type.
+ *
+ */
+public class PromoteKeyOperation extends BaseOperation {
+
+ public PromoteKeyOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ public PromoteKeyResult execute(long masterKeyId) {
+
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_PR, 0);
+
+ // Perform actual type change
+ UncachedKeyRing promotedRing;
+ {
+
+ try {
+
+ // This operation is only allowed for pure public keys
+ // TODO delete secret keys if they are stripped, or have been moved to the card?
+ if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) {
+ log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2);
+ return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
+ }
+
+ log.add(LogType.MSG_PR_FETCHING, 1,
+ KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
+ CanonicalizedPublicKeyRing pubRing =
+ mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
+
+ // create divert-to-card secret key from public key
+ promotedRing = pubRing.createDummySecretRing();
+
+ } catch (PgpKeyNotFoundException e) {
+ log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
+ return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
+ } catch (NotFoundException e) {
+ log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
+ return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
+ }
+ }
+
+ // If the edit operation didn't succeed, exit here
+ if (promotedRing == null) {
+ // error is already logged by modification
+ return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
+ }
+
+ // Check if the action was cancelled
+ if (checkCancelled()) {
+ log.add(LogType.MSG_OPERATION_CANCELLED, 0);
+ return new PromoteKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
+ }
+
+ // Cannot cancel from here on out!
+ setPreventCancel();
+
+ // Save the new keyring.
+ SaveKeyringResult saveResult = mProviderHelper
+ .saveSecretKeyRing(promotedRing, new ProgressScaler(mProgressable, 60, 95, 100));
+ log.add(saveResult, 1);
+
+ // If the save operation didn't succeed, exit here
+ if (!saveResult.success()) {
+ return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
+ }
+
+ updateProgress(R.string.progress_done, 100, 100);
+
+ log.add(LogType.MSG_PR_SUCCESS, 0);
+ return new PromoteKeyResult(PromoteKeyResult.RESULT_OK, log, promotedRing.getMasterKeyId());
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
index 1388c0eac..d025727b5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
@@ -343,6 +343,18 @@ public abstract class OperationResult implements Parcelable {
MSG_IP_UID_REORDER(LogLevel.DEBUG, R.string.msg_ip_uid_reorder),
MSG_IP_UID_PROCESSING (LogLevel.DEBUG, R.string.msg_ip_uid_processing),
MSG_IP_UID_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uid_revoked),
+ MSG_IP_UAT_CLASSIFYING (LogLevel.DEBUG, R.string.msg_ip_uat_classifying),
+ MSG_IP_UAT_PROCESSING_IMAGE (LogLevel.DEBUG, R.string.msg_ip_uat_processing_image),
+ MSG_IP_UAT_PROCESSING_UNKNOWN (LogLevel.DEBUG, R.string.msg_ip_uat_processing_unknown),
+ MSG_IP_UAT_REVOKED (LogLevel.DEBUG, R.string.msg_ip_uat_revoked),
+ MSG_IP_UAT_CERT_BAD (LogLevel.WARN, R.string.msg_ip_uat_cert_bad),
+ MSG_IP_UAT_CERT_OLD (LogLevel.DEBUG, R.string.msg_ip_uat_cert_old),
+ MSG_IP_UAT_CERT_NONREVOKE (LogLevel.DEBUG, R.string.msg_ip_uat_cert_nonrevoke),
+ MSG_IP_UAT_CERT_NEW (LogLevel.DEBUG, R.string.msg_ip_uat_cert_new),
+ MSG_IP_UAT_CERT_ERROR (LogLevel.WARN, R.string.msg_ip_uat_cert_error),
+ MSG_IP_UAT_CERTS_UNKNOWN (LogLevel.DEBUG, R.plurals.msg_ip_uat_certs_unknown),
+ MSG_IP_UAT_CERT_GOOD_REVOKE (LogLevel.DEBUG, R.string.msg_ip_uat_cert_good_revoke),
+ MSG_IP_UAT_CERT_GOOD (LogLevel.DEBUG, R.string.msg_ip_uat_cert_good),
// import secret
MSG_IS(LogLevel.START, R.string.msg_is),
@@ -416,6 +428,21 @@ public abstract class OperationResult implements Parcelable {
MSG_KC_UID_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_old),
MSG_KC_UID_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uid_remove),
MSG_KC_UID_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uid_warn_encoding),
+ MSG_KC_UAT_JPEG (LogLevel.DEBUG, R.string.msg_kc_uat_jpeg),
+ MSG_KC_UAT_UNKNOWN (LogLevel.DEBUG, R.string.msg_kc_uat_unknown),
+ MSG_KC_UAT_BAD_ERR (LogLevel.WARN, R.string.msg_kc_uat_bad_err),
+ MSG_KC_UAT_BAD_LOCAL (LogLevel.WARN, R.string.msg_kc_uat_bad_local),
+ MSG_KC_UAT_BAD_TIME (LogLevel.WARN, R.string.msg_kc_uat_bad_time),
+ MSG_KC_UAT_BAD_TYPE (LogLevel.WARN, R.string.msg_kc_uat_bad_type),
+ MSG_KC_UAT_BAD (LogLevel.WARN, R.string.msg_kc_uat_bad),
+ MSG_KC_UAT_CERT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_cert_dup),
+ MSG_KC_UAT_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_dup),
+ MSG_KC_UAT_FOREIGN (LogLevel.DEBUG, R.string.msg_kc_uat_foreign),
+ MSG_KC_UAT_NO_CERT (LogLevel.DEBUG, R.string.msg_kc_uat_no_cert),
+ MSG_KC_UAT_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_dup),
+ MSG_KC_UAT_REVOKE_OLD (LogLevel.DEBUG, R.string.msg_kc_uat_revoke_old),
+ MSG_KC_UAT_REMOVE (LogLevel.DEBUG, R.string.msg_kc_uat_remove),
+ MSG_KC_UAT_WARN_ENCODING (LogLevel.WARN, R.string.msg_kc_uat_warn_encoding),
// keyring consolidation
@@ -446,6 +473,7 @@ public abstract class OperationResult implements Parcelable {
// secret key modify
MSG_MF (LogLevel.START, R.string.msg_mr),
+ MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint),
MSG_MF_ERROR_KEYID (LogLevel.ERROR, R.string.msg_mf_error_keyid),
@@ -458,6 +486,7 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
MSG_MF_ERROR_PGP (LogLevel.ERROR, R.string.msg_mf_error_pgp),
+ MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted),
MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),
MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),
MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing),
@@ -480,6 +509,9 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary),
MSG_MF_UID_REVOKE (LogLevel.INFO, R.string.msg_mf_uid_revoke),
MSG_MF_UID_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_mf_uid_error_empty),
+ MSG_MF_UAT_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_mf_uat_error_empty),
+ MSG_MF_UAT_ADD_IMAGE (LogLevel.INFO, R.string.msg_mf_uat_add_image),
+ MSG_MF_UAT_ADD_UNKNOWN (LogLevel.INFO, R.string.msg_mf_uat_add_unknown),
MSG_MF_UNLOCK_ERROR (LogLevel.ERROR, R.string.msg_mf_unlock_error),
MSG_MF_UNLOCK (LogLevel.DEBUG, R.string.msg_mf_unlock),
@@ -516,6 +548,13 @@ public abstract class OperationResult implements Parcelable {
MSG_ED_FETCHING (LogLevel.DEBUG, R.string.msg_ed_fetching),
MSG_ED_SUCCESS (LogLevel.OK, R.string.msg_ed_success),
+ // promote key
+ MSG_PR (LogLevel.START, R.string.msg_pr),
+ MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret),
+ MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found),
+ MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
+ MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
+
// messages used in UI code
MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert),
MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy),
@@ -696,7 +735,7 @@ public abstract class OperationResult implements Parcelable {
public static class OperationLog implements Iterable {
- private final List mParcels = new ArrayList();
+ private final List mParcels = new ArrayList<>();
/// Simple convenience method
public void add(LogType type, int indent, Object... parameters) {
@@ -721,7 +760,7 @@ public abstract class OperationResult implements Parcelable {
}
public boolean containsType(LogType type) {
- for(LogEntryParcel entry : new IterableIterator(mParcels.iterator())) {
+ for(LogEntryParcel entry : new IterableIterator<>(mParcels.iterator())) {
if (entry.mType == type) {
return true;
}
@@ -730,7 +769,7 @@ public abstract class OperationResult implements Parcelable {
}
public boolean containsWarnings() {
- for(LogEntryParcel entry : new IterableIterator(mParcels.iterator())) {
+ for(LogEntryParcel entry : new IterableIterator<>(mParcels.iterator())) {
if (entry.mType.mLevel == LogLevel.WARN || entry.mType.mLevel == LogLevel.ERROR) {
return true;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java
new file mode 100644
index 000000000..af9aff84a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann
+ * Copyright (C) 2014 Vincent Breitmoser
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.operations.results;
+
+import android.os.Parcel;
+
+public class PromoteKeyResult extends OperationResult {
+
+ public final Long mMasterKeyId;
+
+ public PromoteKeyResult(int result, OperationLog log, Long masterKeyId) {
+ super(result, log);
+ mMasterKeyId = masterKeyId;
+ }
+
+ public PromoteKeyResult(Parcel source) {
+ super(source);
+ mMasterKeyId = source.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeLong(mMasterKeyId);
+ }
+
+ public static Creator CREATOR = new Creator() {
+ public PromoteKeyResult createFromParcel(final Parcel source) {
+ return new PromoteKeyResult(source);
+ }
+
+ public PromoteKeyResult[] newArray(final int size) {
+ return new PromoteKeyResult[size];
+ }
+ };
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
index db0a9b137..bbf136dac 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
@@ -19,7 +19,6 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
index 3539a4ceb..b026d9257 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
@@ -53,7 +53,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canSign() {
// if key flags subpacket is available, honor it!
- if (getKeyUsage() != null) {
+ if (getKeyUsage() != 0) {
return (getKeyUsage() & KeyFlags.SIGN_DATA) != 0;
}
@@ -66,7 +66,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canCertify() {
// if key flags subpacket is available, honor it!
- if (getKeyUsage() != null) {
+ if (getKeyUsage() != 0) {
return (getKeyUsage() & KeyFlags.CERTIFY_OTHER) != 0;
}
@@ -79,7 +79,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canEncrypt() {
// if key flags subpacket is available, honor it!
- if (getKeyUsage() != null) {
+ if (getKeyUsage() != 0) {
return (getKeyUsage() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0;
}
@@ -93,7 +93,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
public boolean canAuthenticate() {
// if key flags subpacket is available, honor it!
- if (getKeyUsage() != null) {
+ if (getKeyUsage() != 0) {
return (getKeyUsage() & KeyFlags.AUTHENTICATION) != 0;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
index 85ef3eaa4..c2506685d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.pgp;
+import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -76,7 +78,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
public IterableIterator publicKeyIterator() {
@SuppressWarnings("unchecked")
final Iterator it = getRing().getPublicKeys();
- return new IterableIterator(new Iterator() {
+ return new IterableIterator<>(new Iterator() {
@Override
public boolean hasNext() {
return it.hasNext();
@@ -94,4 +96,13 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
});
}
+ /** Create a dummy secret ring from this key */
+ public UncachedKeyRing createDummySecretRing () {
+
+ PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
+ S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
+ return new UncachedKeyRing(secRing);
+
+ }
+
}
\ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
index cffb09420..40f2f48ad 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
@@ -182,7 +182,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
* @return
*/
public LinkedList getSupportedHashAlgorithms() {
- LinkedList supported = new LinkedList();
+ LinkedList supported = new LinkedList<>();
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
// No support for MD5
@@ -262,11 +262,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
- } catch (PgpKeyNotFoundException e) {
+ } catch (PgpKeyNotFoundException | PGPException e) {
// TODO: simply throw PGPException!
throw new PgpGeneralException("Error initializing signature!", e);
- } catch (PGPException e) {
- throw new PgpGeneralException("Error initializing signature!", e);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
index eb589c3f9..b5f6a5b09 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
@@ -18,27 +18,19 @@
package org.sufficientlysecure.keychain.pgp;
-import org.spongycastle.bcpg.S2K;
-import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
-import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
-import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
-import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.provider.KeychainContract;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
@@ -94,7 +86,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
public IterableIterator secretKeyIterator() {
final Iterator it = mRing.getSecretKeys();
- return new IterableIterator(new Iterator() {
+ return new IterableIterator<>(new Iterator() {
@Override
public boolean hasNext() {
return it.hasNext();
@@ -114,7 +106,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
public IterableIterator publicKeyIterator() {
final Iterator it = getRing().getPublicKeys();
- return new IterableIterator(new Iterator() {
+ return new IterableIterator<>(new Iterator() {
@Override
public boolean hasNext() {
return it.hasNext();
@@ -133,7 +125,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
}
public HashMap getLocalNotationData() {
- HashMap result = new HashMap();
+ HashMap result = new HashMap<>();
Iterator it = getRing().getPublicKey().getKeySignatures();
while (it.hasNext()) {
WrappedSignature sig = new WrappedSignature(it.next());
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
index aa324c7ed..ed4715681 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.Log;
@@ -33,7 +32,7 @@ public class OpenPgpSignatureResultBuilder {
// OpenPgpSignatureResult
private boolean mSignatureOnly = false;
private String mPrimaryUserId;
- private ArrayList mUserIds = new ArrayList();
+ private ArrayList mUserIds = new ArrayList<>();
private long mKeyId;
// builder
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
index 128928bb3..aebb52a03 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -34,6 +34,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
@@ -134,7 +135,7 @@ public class PgpKeyOperation {
public PgpKeyOperation(Progressable progress) {
super();
if (progress != null) {
- mProgress = new Stack();
+ mProgress = new Stack<>();
mProgress.push(progress);
}
}
@@ -287,13 +288,11 @@ public class PgpKeyOperation {
// build new key pair
return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
- } catch(NoSuchProviderException e) {
+ } catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
log.add(LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null;
- } catch(InvalidAlgorithmParameterException e) {
- throw new RuntimeException(e);
} catch(PGPException e) {
Log.e(Constants.TAG, "internal pgp error", e);
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -388,6 +387,9 @@ public class PgpKeyOperation {
* with a passphrase fails, the operation will fail with an unlocking error. More specific
* handling of errors should be done in UI code!
*
+ * If the passphrase is null, only a restricted subset of operations will be available,
+ * namely stripping of subkeys and changing the protection mode of dummy keys.
+ *
*/
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
String passphrase) {
@@ -428,6 +430,11 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ // If we have no passphrase, only allow restricted operation
+ if (passphrase == null) {
+ return internalRestricted(sKR, saveParcel, log);
+ }
+
// read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
@@ -478,7 +485,7 @@ public class PgpKeyOperation {
PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids
- subProgressPush(15, 25);
+ subProgressPush(15, 23);
for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {
progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size()));
@@ -495,7 +502,7 @@ public class PgpKeyOperation {
@SuppressWarnings("unchecked")
Iterator it = modifiedPublicKey.getSignaturesForID(userId);
if (it != null) {
- for (PGPSignature cert : new IterableIterator(it)) {
+ for (PGPSignature cert : new IterableIterator<>(it)) {
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
// foreign certificate?! error error error
log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
@@ -522,8 +529,37 @@ public class PgpKeyOperation {
}
subProgressPop();
- // 2b. Add revocations for revoked user ids
- subProgressPush(25, 40);
+ // 2b. Add certificates for new user ids
+ subProgressPush(23, 32);
+ for (int i = 0; i < saveParcel.mAddUserAttribute.size(); i++) {
+
+ progress(R.string.progress_modify_adduat, (i - 1) * (100 / saveParcel.mAddUserAttribute.size()));
+ WrappedUserAttribute attribute = saveParcel.mAddUserAttribute.get(i);
+
+ switch (attribute.getType()) {
+ // the 'none' type must not succeed
+ case WrappedUserAttribute.UAT_NONE:
+ log.add(LogType.MSG_MF_UAT_ERROR_EMPTY, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ case WrappedUserAttribute.UAT_IMAGE:
+ log.add(LogType.MSG_MF_UAT_ADD_IMAGE, indent);
+ break;
+ default:
+ log.add(LogType.MSG_MF_UAT_ADD_UNKNOWN, indent);
+ break;
+ }
+
+ PGPUserAttributeSubpacketVector vector = attribute.getVector();
+
+ // generate and add new certificate
+ PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
+ masterPublicKey, vector);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
+ }
+ subProgressPop();
+
+ // 2c. Add revocations for revoked user ids
+ subProgressPush(32, 40);
for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) {
progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size()));
@@ -685,6 +721,27 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ if (change.mDummyStrip || change.mDummyDivert != null) {
+ // IT'S DANGEROUS~
+ // no really, it is. this operation irrevocably removes the private key data from the key
+ if (change.mDummyStrip) {
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ } else {
+ // the serial number must be 16 bytes in length
+ if (change.mDummyDivert.length != 16) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ }
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+ }
+
+ // This doesn't concern us any further
+ if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
+ continue;
+ }
+
// expiry must not be in the past
if (change.mExpiry != null && change.mExpiry != 0 &&
new Date(change.mExpiry*1000).before(new Date())) {
@@ -775,30 +832,6 @@ public class PgpKeyOperation {
}
subProgressPop();
- // 4c. For each subkey to be stripped... do so
- subProgressPush(65, 70);
- for (int i = 0; i < saveParcel.mStripSubKeys.size(); i++) {
-
- progress(R.string.progress_modify_subkeystrip, (i-1) * (100 / saveParcel.mStripSubKeys.size()));
- long strip = saveParcel.mStripSubKeys.get(i);
- log.add(LogType.MSG_MF_SUBKEY_STRIP,
- indent, KeyFormattingUtils.convertKeyIdToHex(strip));
-
- PGPSecretKey sKey = sKR.getSecretKey(strip);
- if (sKey == null) {
- log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
- indent+1, KeyFormattingUtils.convertKeyIdToHex(strip));
- return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
- }
-
- // IT'S DANGEROUS~
- // no really, it is. this operation irrevocably removes the private key data from the key
- sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
-
- }
- subProgressPop();
-
// 5. Generate and add new subkeys
subProgressPush(70, 90);
for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {
@@ -907,6 +940,73 @@ public class PgpKeyOperation {
}
+ /** This method does the actual modifications in a keyring just like internal, except it
+ * supports only the subset of operations which require no passphrase, and will error
+ * otherwise.
+ */
+ private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
+ OperationLog log) {
+
+ int indent = 1;
+
+ progress(R.string.progress_modify, 0);
+
+ // Make sure the saveParcel includes only operations available without passphrae!
+ if (!saveParcel.isRestrictedOnly()) {
+ log.add(LogType.MSG_MF_ERROR_RESTRICTED, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ // Check if we were cancelled
+ if (checkCancelled()) {
+ log.add(LogType.MSG_OPERATION_CANCELLED, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
+ }
+
+ // The only operation we can do here:
+ // 4a. Strip secret keys, or change their protection mode (stripped/divert-to-card)
+ subProgressPush(50, 60);
+ for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
+
+ progress(R.string.progress_modify_subkeychange, (i - 1) * (100 / saveParcel.mChangeSubKeys.size()));
+ SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i);
+ log.add(LogType.MSG_MF_SUBKEY_CHANGE,
+ indent, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+
+ PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
+ if (sKey == null) {
+ log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ if (change.mDummyStrip || change.mDummyDivert != null) {
+ // IT'S DANGEROUS~
+ // no really, it is. this operation irrevocably removes the private key data from the key
+ if (change.mDummyStrip) {
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ } else {
+ // the serial number must be 16 bytes in length
+ if (change.mDummyDivert.length != 16) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
+ }
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+ }
+
+ }
+
+ // And we're done!
+ progress(R.string.progress_done, 100);
+ log.add(LogType.MSG_MF_SUCCESS, indent);
+ return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
+
+ }
+
+
private static PGPSecretKeyRing applyNewUnlock(
PGPSecretKeyRing sKR,
PGPPublicKey masterPublicKey,
@@ -1174,6 +1274,26 @@ public class PgpKeyOperation {
return sGen.generateCertification(userId, pKey);
}
+ private static PGPSignature generateUserAttributeSignature(
+ PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
+ PGPUserAttributeSubpacketVector vector)
+ throws IOException, PGPException, SignatureException {
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ {
+ /* critical subpackets: we consider those important for a modern pgp implementation */
+ hashedPacketsGen.setSignatureCreationTime(true, new Date());
+ }
+
+ sGen.setHashedSubpackets(hashedPacketsGen.generate());
+ sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+ return sGen.generateCertification(vector, pKey);
+ }
+
private static PGPSignature generateRevocationSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
index a445e161f..af85bd878 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
+import org.spongycastle.bcpg.UserAttributeSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
@@ -30,6 +31,7 @@ import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.sufficientlysecure.keychain.Constants;
@@ -443,7 +445,7 @@ public class UncachedKeyRing {
}
}
- ArrayList processedUserIds = new ArrayList();
+ ArrayList processedUserIds = new ArrayList<>();
for (byte[] rawUserId : new IterableIterator(masterKey.getRawUserIDs())) {
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
@@ -468,7 +470,7 @@ public class UncachedKeyRing {
@SuppressWarnings("unchecked")
Iterator signaturesIt = masterKey.getSignaturesForID(rawUserId);
if (signaturesIt != null) {
- for (PGPSignature zert : new IterableIterator(signaturesIt)) {
+ for (PGPSignature zert : new IterableIterator<>(signaturesIt)) {
WrappedSignature cert = new WrappedSignature(zert);
long certId = cert.getKeyId();
@@ -605,6 +607,170 @@ public class UncachedKeyRing {
return null;
}
+ ArrayList processedUserAttributes = new ArrayList<>();
+ for (PGPUserAttributeSubpacketVector userAttribute :
+ new IterableIterator(masterKey.getUserAttributes())) {
+
+ if (userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE) != null) {
+ log.add(LogType.MSG_KC_UAT_JPEG, indent);
+ } else {
+ log.add(LogType.MSG_KC_UAT_UNKNOWN, indent);
+ }
+
+ try {
+ indent += 1;
+
+ // check for duplicate user attributes
+ if (processedUserAttributes.contains(userAttribute)) {
+ log.add(LogType.MSG_KC_UAT_DUP, indent);
+ // strip out the first found user id with this name
+ modified = PGPPublicKey.removeCertification(modified, userAttribute);
+ }
+ processedUserAttributes.add(userAttribute);
+
+ PGPSignature selfCert = null;
+ revocation = null;
+
+ // look through signatures for this specific user id
+ @SuppressWarnings("unchecked")
+ Iterator signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute);
+ if (signaturesIt != null) {
+ for (PGPSignature zert : new IterableIterator<>(signaturesIt)) {
+ WrappedSignature cert = new WrappedSignature(zert);
+ long certId = cert.getKeyId();
+
+ int type = zert.getSignatureType();
+ if (type != PGPSignature.DEFAULT_CERTIFICATION
+ && type != PGPSignature.NO_CERTIFICATION
+ && type != PGPSignature.CASUAL_CERTIFICATION
+ && type != PGPSignature.POSITIVE_CERTIFICATION
+ && type != PGPSignature.CERTIFICATION_REVOCATION) {
+ log.add(LogType.MSG_KC_UAT_BAD_TYPE,
+ indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ badCerts += 1;
+ continue;
+ }
+
+ if (cert.getCreationTime().after(nowPlusOneDay)) {
+ // Creation date in the future? No way!
+ log.add(LogType.MSG_KC_UAT_BAD_TIME, indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ badCerts += 1;
+ continue;
+ }
+
+ if (cert.isLocal()) {
+ // Creation date in the future? No way!
+ log.add(LogType.MSG_KC_UAT_BAD_LOCAL, indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ badCerts += 1;
+ continue;
+ }
+
+ // If this is a foreign signature, ...
+ if (certId != masterKeyId) {
+ // never mind any further for public keys, but remove them from secret ones
+ if (isSecret()) {
+ log.add(LogType.MSG_KC_UAT_FOREIGN,
+ indent, KeyFormattingUtils.convertKeyIdToHex(certId));
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ badCerts += 1;
+ }
+ continue;
+ }
+
+ // Otherwise, first make sure it checks out
+ try {
+ cert.init(masterKey);
+ if (!cert.verifySignature(masterKey, userAttribute)) {
+ log.add(LogType.MSG_KC_UAT_BAD,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ badCerts += 1;
+ continue;
+ }
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_KC_UAT_BAD_ERR,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ badCerts += 1;
+ continue;
+ }
+
+ switch (type) {
+ case PGPSignature.DEFAULT_CERTIFICATION:
+ case PGPSignature.NO_CERTIFICATION:
+ case PGPSignature.CASUAL_CERTIFICATION:
+ case PGPSignature.POSITIVE_CERTIFICATION:
+ if (selfCert == null) {
+ selfCert = zert;
+ } else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
+ log.add(LogType.MSG_KC_UAT_CERT_DUP,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, selfCert);
+ redundantCerts += 1;
+ selfCert = zert;
+ } else {
+ log.add(LogType.MSG_KC_UAT_CERT_DUP,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ redundantCerts += 1;
+ }
+ // If there is a revocation certificate, and it's older than this, drop it
+ if (revocation != null
+ && revocation.getCreationTime().before(selfCert.getCreationTime())) {
+ log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
+ revocation = null;
+ redundantCerts += 1;
+ }
+ break;
+
+ case PGPSignature.CERTIFICATION_REVOCATION:
+ // If this is older than the (latest) self cert, drop it
+ if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
+ log.add(LogType.MSG_KC_UAT_REVOKE_OLD,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ redundantCerts += 1;
+ continue;
+ }
+ // first revocation? remember it.
+ if (revocation == null) {
+ revocation = zert;
+ // more revocations? at least one is superfluous, then.
+ } else if (revocation.getCreationTime().before(cert.getCreationTime())) {
+ log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, revocation);
+ redundantCerts += 1;
+ revocation = zert;
+ } else {
+ log.add(LogType.MSG_KC_UAT_REVOKE_DUP,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute, zert);
+ redundantCerts += 1;
+ }
+ break;
+ }
+ }
+ }
+
+ // If no valid certificate (if only a revocation) remains, drop it
+ if (selfCert == null && revocation == null) {
+ log.add(LogType.MSG_KC_UAT_REMOVE,
+ indent);
+ modified = PGPPublicKey.removeCertification(modified, userAttribute);
+ }
+
+ } finally {
+ indent -= 1;
+ }
+ }
+
+
// Replace modified key in the keyring
ring = replacePublicKey(ring, modified);
indent -= 1;
@@ -612,7 +778,7 @@ public class UncachedKeyRing {
}
// Keep track of ids we encountered so far
- Set knownIds = new HashSet();
+ Set knownIds = new HashSet<>();
// Process all keys
for (PGPPublicKey key : new IterableIterator(ring.getPublicKeys())) {
@@ -852,8 +1018,8 @@ public class UncachedKeyRing {
/** This operation merges information from a different keyring, returning a combined
* UncachedKeyRing.
*
- * The combined keyring contains the subkeys and user ids of both input keyrings, but it does
- * not necessarily have the canonicalized property.
+ * The combined keyring contains the subkeys, user ids and user attributes of both input
+ * keyrings, but it does not necessarily have the canonicalized property.
*
* @param other The UncachedKeyRing to merge. Must not be empty, and of the same masterKeyId
* @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as
@@ -876,7 +1042,7 @@ public class UncachedKeyRing {
}
// remember which certs we already added. this is cheaper than semantic deduplication
- Set certs = new TreeSet(new Comparator() {
+ Set certs = new TreeSet<>(new Comparator() {
public int compare(byte[] left, byte[] right) {
// check for length equality
if (left.length != right.length) {
@@ -958,7 +1124,7 @@ public class UncachedKeyRing {
if (signaturesIt == null) {
continue;
}
- for (PGPSignature cert : new IterableIterator(signaturesIt)) {
+ for (PGPSignature cert : new IterableIterator<>(signaturesIt)) {
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
@@ -973,6 +1139,32 @@ public class UncachedKeyRing {
modified = PGPPublicKey.addCertification(modified, rawUserId, cert);
}
}
+
+ // Copy over all user attribute certificates
+ for (PGPUserAttributeSubpacketVector vector :
+ new IterableIterator(key.getUserAttributes())) {
+ @SuppressWarnings("unchecked")
+ Iterator signaturesIt = key.getSignaturesForUserAttribute(vector);
+ // no signatures for this user attribute attribute, skip it
+ if (signaturesIt == null) {
+ continue;
+ }
+ for (PGPSignature cert : new IterableIterator<>(signaturesIt)) {
+ // Don't merge foreign stuff into secret keys
+ if (cert.getKeyID() != masterKeyId && isSecret()) {
+ continue;
+ }
+ byte[] encoded = cert.getEncoded();
+ // Known cert, skip it
+ if (certs.contains(encoded)) {
+ continue;
+ }
+ newCerts += 1;
+ certs.add(encoded);
+ modified = PGPPublicKey.addCertification(modified, vector, cert);
+ }
+ }
+
// If anything changed, save the updated (sub)key
if (modified != resultKey) {
result = replacePublicKey(result, modified);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
index fe3ab96a5..0fe1ccdb6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
@@ -20,10 +20,10 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ECPublicBCPGKey;
import org.spongycastle.bcpg.SignatureSubpacketTags;
-import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -135,7 +135,7 @@ public class UncachedPublicKey {
continue;
}
- for (PGPSignature sig : new IterableIterator(signaturesIt)) {
+ for (PGPSignature sig : new IterableIterator<>(signaturesIt)) {
try {
// if this is a revocation, this is not the user id
@@ -199,7 +199,7 @@ public class UncachedPublicKey {
}
public ArrayList getUnorderedUserIds() {
- ArrayList userIds = new ArrayList();
+ ArrayList userIds = new ArrayList<>();
for (byte[] rawUserId : new IterableIterator(mPublicKey.getRawUserIDs())) {
// use our decoding method
userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId));
@@ -208,13 +208,22 @@ public class UncachedPublicKey {
}
public ArrayList getUnorderedRawUserIds() {
- ArrayList userIds = new ArrayList();
+ ArrayList userIds = new ArrayList<>();
for (byte[] userId : new IterableIterator(mPublicKey.getRawUserIDs())) {
userIds.add(userId);
}
return userIds;
}
+ public ArrayList getUnorderedUserAttributes() {
+ ArrayList userAttributes = new ArrayList<>();
+ for (PGPUserAttributeSubpacketVector userAttribute :
+ new IterableIterator(mPublicKey.getUserAttributes())) {
+ userAttributes.add(new WrappedUserAttribute(userAttribute));
+ }
+ return userAttributes;
+ }
+
public boolean isElGamalEncrypt() {
return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT;
}
@@ -270,33 +279,83 @@ public class UncachedPublicKey {
}
}
+ public Iterator getSignaturesForUserAttribute(WrappedUserAttribute attribute) {
+ final Iterator it = mPublicKey.getSignaturesForUserAttribute(attribute.getVector());
+ if (it != null) {
+ return new Iterator() {
+ public void remove() {
+ it.remove();
+ }
+ public WrappedSignature next() {
+ return new WrappedSignature(it.next());
+ }
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+ };
+ } else {
+ return null;
+ }
+ }
+
/** Get all key usage flags.
* If at least one key flag subpacket is present return these. If no
* subpacket is present it returns null.
*
* Note that this method has package visiblity because it is used in test
* cases. Certificates of UncachedPublicKey instances can NOT be assumed to
- * be verified, so the result of this method should not be used in other
- * places!
+ * be verified or even by the correct key, so the result of this method
+ * should never be used in other places!
*/
@SuppressWarnings("unchecked")
Integer getKeyUsage() {
if (mCacheUsage == null) {
+ PGPSignature mostRecentSig = null;
for (PGPSignature sig : new IterableIterator(mPublicKey.getSignatures())) {
if (mPublicKey.isMasterKey() && sig.getKeyID() != mPublicKey.getKeyID()) {
continue;
}
- PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
+ switch (sig.getSignatureType()) {
+ case PGPSignature.DEFAULT_CERTIFICATION:
+ case PGPSignature.POSITIVE_CERTIFICATION:
+ case PGPSignature.CASUAL_CERTIFICATION:
+ case PGPSignature.NO_CERTIFICATION:
+ case PGPSignature.SUBKEY_BINDING:
+ break;
+ // if this is not one of the above types, don't care
+ default:
+ continue;
+ }
+
+ // If we have no sig yet, take the first we can get
+ if (mostRecentSig == null) {
+ mostRecentSig = sig;
+ continue;
+ }
+
+ // If the new sig is less recent, skip it
+ if (mostRecentSig.getCreationTime().after(sig.getCreationTime())) {
+ continue;
+ }
+
+ // Otherwise, note it down as the new "most recent" one
+ mostRecentSig = sig;
+ }
+
+ // Initialize to 0 as cached but empty value, if there is no sig (can't happen
+ // for canonicalized keyring), or there is no KEY_FLAGS packet in the sig
+ mCacheUsage = 0;
+ if (mostRecentSig != null) {
+ // If a mostRecentSig has been found, (cache and) return its flags
+ PGPSignatureSubpacketVector hashed = mostRecentSig.getHashedSubPackets();
if (hashed != null && hashed.getSubpacket(SignatureSubpacketTags.KEY_FLAGS) != null) {
- // init if at least one key flag subpacket has been found
- if (mCacheUsage == null) {
- mCacheUsage = 0;
- }
- mCacheUsage |= hashed.getKeyFlags();
+ mCacheUsage = hashed.getKeyFlags();
}
}
+
}
return mCacheUsage;
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
index c395ca52d..ade075d55 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
@@ -29,13 +29,13 @@ import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
-import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -79,7 +79,7 @@ public class WrappedSignature {
}
public ArrayList getEmbeddedSignatures() {
- ArrayList sigs = new ArrayList();
+ ArrayList sigs = new ArrayList<>();
if (!mSig.hasSubpackets()) {
return sigs;
}
@@ -199,12 +199,23 @@ public class WrappedSignature {
}
}
+ boolean verifySignature(PGPPublicKey key, PGPUserAttributeSubpacketVector attribute) throws PgpGeneralException {
+ try {
+ return mSig.verifyCertification(attribute, key);
+ } catch (PGPException e) {
+ throw new PgpGeneralException("Error!", e);
+ }
+ }
+
public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), rawUserId);
}
public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), uid);
}
+ public boolean verifySignature(UncachedPublicKey key, WrappedUserAttribute attribute) throws PgpGeneralException {
+ return verifySignature(key.getPublicKey(), attribute.getVector());
+ }
public static WrappedSignature fromBytes(byte[] data) {
PGPObjectFactory factory = new PGPObjectFactory(data);
@@ -243,7 +254,7 @@ public class WrappedSignature {
}
public HashMap getNotation() {
- HashMap result = new HashMap();
+ HashMap result = new HashMap<>();
// If there is any notation data
if (mSig.getHashedSubPackets() != null
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java
new file mode 100644
index 000000000..da6d8b287
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 Vincent Breitmoser
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.pgp;
+
+import org.spongycastle.bcpg.BCPGInputStream;
+import org.spongycastle.bcpg.BCPGOutputStream;
+import org.spongycastle.bcpg.Packet;
+import org.spongycastle.bcpg.UserAttributePacket;
+import org.spongycastle.bcpg.UserAttributeSubpacket;
+import org.spongycastle.bcpg.UserAttributeSubpacketInputStream;
+import org.spongycastle.bcpg.UserAttributeSubpacketTags;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class WrappedUserAttribute implements Serializable {
+
+ public static final int UAT_NONE = 0;
+ public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE;
+
+ private PGPUserAttributeSubpacketVector mVector;
+
+ WrappedUserAttribute(PGPUserAttributeSubpacketVector vector) {
+ mVector = vector;
+ }
+
+ PGPUserAttributeSubpacketVector getVector() {
+ return mVector;
+ }
+
+ public int getType() {
+ UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray();
+ if (subpackets.length > 0) {
+ return subpackets[0].getType();
+ }
+ return 0;
+ }
+
+ public static WrappedUserAttribute fromSubpacket (int type, byte[] data) {
+ UserAttributeSubpacket subpacket = new UserAttributeSubpacket(type, data);
+ PGPUserAttributeSubpacketVector vector = new PGPUserAttributeSubpacketVector(
+ new UserAttributeSubpacket[] { subpacket });
+
+ return new WrappedUserAttribute(vector);
+
+ }
+
+ public byte[] getEncoded () throws IOException {
+ UserAttributeSubpacket[] subpackets = mVector.toSubpacketArray();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (UserAttributeSubpacket subpacket : subpackets) {
+ subpacket.encode(out);
+ }
+ return out.toByteArray();
+ }
+
+ public static WrappedUserAttribute fromData (byte[] data) throws IOException {
+ UserAttributeSubpacketInputStream in =
+ new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data));
+ ArrayList list = new ArrayList();
+ while (in.available() > 0) {
+ list.add(in.readPacket());
+ }
+ UserAttributeSubpacket[] result = new UserAttributeSubpacket[list.size()];
+ list.toArray(result);
+ return new WrappedUserAttribute(
+ new PGPUserAttributeSubpacketVector(result));
+ }
+
+ /** Writes this object to an ObjectOutputStream. */
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ BCPGOutputStream bcpg = new BCPGOutputStream(baos);
+ bcpg.writePacket(new UserAttributePacket(mVector.toSubpacketArray()));
+ out.writeObject(baos.toByteArray());
+
+ }
+
+ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+
+ byte[] data = (byte[]) in.readObject();
+ BCPGInputStream bcpg = new BCPGInputStream(new ByteArrayInputStream(data));
+ Packet p = bcpg.readPacket();
+ if ( ! UserAttributePacket.class.isInstance(p)) {
+ throw new IOException("Could not decode UserAttributePacket!");
+ }
+ mVector = new PGPUserAttributeSubpacketVector(((UserAttributePacket) p).getSubpackets());
+
+ }
+
+ private void readObjectNoData() throws ObjectStreamException {
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!WrappedUserAttribute.class.isInstance(o)) {
+ return false;
+ }
+ return mVector.equals(((WrappedUserAttribute) o).mVector);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
index 2c02e429d..f4e00c36c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -51,9 +51,11 @@ public class KeychainContract {
String EXPIRY = "expiry";
}
- interface UserIdsColumns {
+ interface UserPacketsColumns {
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
+ String TYPE = "type"; // not a database id
String USER_ID = "user_id"; // not a database id
+ String ATTRIBUTE_DATA = "attribute_data"; // not a database id
String RANK = "rank"; // ONLY used for sorting! no key, no nothing!
String IS_PRIMARY = "is_primary";
String IS_REVOKED = "is_revoked";
@@ -105,7 +107,7 @@ public class KeychainContract {
public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ACCOUNTS = "accounts";
- public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns {
+ public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
public static final String IS_REVOKED = KeysColumns.IS_REVOKED;
public static final String VERIFIED = CertsColumns.VERIFIED;
@@ -225,7 +227,7 @@ public class KeychainContract {
}
- public static class UserIds implements UserIdsColumns, BaseColumns {
+ public static class UserPackets implements UserPacketsColumns, BaseColumns {
public static final String VERIFIED = "verified";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
@@ -304,7 +306,7 @@ public class KeychainContract {
}
public static class Certs implements CertsColumns, BaseColumns {
- public static final String USER_ID = UserIdsColumns.USER_ID;
+ public static final String USER_ID = UserPacketsColumns.USER_ID;
public static final String SIGNER_UID = "signer_user_id";
public static final int UNVERIFIED = 0;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index 84a50dc65..5ce5eec17 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -33,7 +33,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
-import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
import org.sufficientlysecure.keychain.util.Log;
@@ -52,7 +52,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
- private static final int DATABASE_VERSION = 6;
+ private static final int DATABASE_VERSION = 7;
static Boolean apgHack = false;
private Context mContext;
@@ -60,7 +60,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS_PUBLIC = "keyrings_public";
String KEY_RINGS_SECRET = "keyrings_secret";
String KEYS = "keys";
- String USER_IDS = "user_ids";
+ String USER_PACKETS = "user_ids";
String CERTS = "certs";
String API_APPS = "api_apps";
String API_ACCOUNTS = "api_accounts";
@@ -106,18 +106,20 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
- private static final String CREATE_USER_IDS =
- "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS + "("
- + UserIdsColumns.MASTER_KEY_ID + " INTEGER, "
- + UserIdsColumns.USER_ID + " TEXT, "
+ private static final String CREATE_USER_PACKETS =
+ "CREATE TABLE IF NOT EXISTS " + Tables.USER_PACKETS + "("
+ + UserPacketsColumns.MASTER_KEY_ID + " INTEGER, "
+ + UserPacketsColumns.TYPE + " INT, "
+ + UserPacketsColumns.USER_ID + " TEXT, "
+ + UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, "
- + UserIdsColumns.IS_PRIMARY + " INTEGER, "
- + UserIdsColumns.IS_REVOKED + " INTEGER, "
- + UserIdsColumns.RANK+ " INTEGER, "
+ + UserPacketsColumns.IS_PRIMARY + " INTEGER, "
+ + UserPacketsColumns.IS_REVOKED + " INTEGER, "
+ + UserPacketsColumns.RANK+ " INTEGER, "
- + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), "
- + "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), "
- + "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES "
+ + "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.USER_ID + "), "
+ + "UNIQUE (" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), "
+ + "FOREIGN KEY(" + UserPacketsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
@@ -138,7 +140,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE,"
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES "
- + Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE"
+ + Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_API_APPS =
@@ -189,7 +191,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYRINGS_PUBLIC);
db.execSQL(CREATE_KEYRINGS_SECRET);
db.execSQL(CREATE_KEYS);
- db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_USER_PACKETS);
db.execSQL(CREATE_CERTS);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
@@ -238,6 +240,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
case 5:
// do consolidate for 3.0 beta3
// fall through
+ case 6:
+ db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
+ db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
}
// always do consolidate after upgrade
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index d40287690..72475472e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -37,7 +37,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
-import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.Log;
@@ -205,7 +206,7 @@ public class KeychainProvider extends ContentProvider {
return Keys.CONTENT_TYPE;
case KEY_RING_USER_IDS:
- return UserIds.CONTENT_TYPE;
+ return UserPackets.CONTENT_TYPE;
case KEY_RING_SECRET:
return KeyRings.CONTENT_ITEM_TYPE;
@@ -247,7 +248,7 @@ public class KeychainProvider extends ContentProvider {
case KEY_RINGS_UNIFIED:
case KEY_RINGS_FIND_BY_EMAIL:
case KEY_RINGS_FIND_BY_SUBKEY: {
- HashMap projectionMap = new HashMap();
+ HashMap projectionMap = new HashMap<>();
projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id");
projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID);
@@ -262,7 +263,7 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY);
projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM);
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
- projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID);
+ projectionMap.put(KeyRings.USER_ID, UserPackets.USER_ID);
projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED);
projectionMap.put(KeyRings.PUBKEY_DATA,
Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA
@@ -296,11 +297,12 @@ public class KeychainProvider extends ContentProvider {
qb.setTables(
Tables.KEYS
- + " INNER JOIN " + Tables.USER_IDS + " ON ("
+ + " INNER JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = "
- + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
- + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0"
+ + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ // we KNOW that the rank zero user packet is a user id!
+ + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = 0"
+ ") LEFT JOIN " + Tables.CERTS + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = "
@@ -376,7 +378,7 @@ public class KeychainProvider extends ContentProvider {
String subkey = Long.valueOf(uri.getLastPathSegment()).toString();
qb.appendWhere(" AND EXISTS ("
+ " SELECT 1 FROM " + Tables.KEYS + " AS tmp"
- + " WHERE tmp." + UserIds.MASTER_KEY_ID
+ + " WHERE tmp." + UserPackets.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND tmp." + Keys.KEY_ID + " = " + subkey + ""
+ ")");
@@ -398,15 +400,15 @@ public class KeychainProvider extends ContentProvider {
if (i != 0) {
emailWhere += " OR ";
}
- emailWhere += "tmp." + UserIds.USER_ID + " LIKE ";
+ emailWhere += "tmp." + UserPackets.USER_ID + " LIKE ";
// match '*', so it has to be at the *end* of the user id
emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
gotCondition = true;
}
if(gotCondition) {
qb.appendWhere(" AND EXISTS ("
- + " SELECT 1 FROM " + Tables.USER_IDS + " AS tmp"
- + " WHERE tmp." + UserIds.MASTER_KEY_ID
+ + " SELECT 1 FROM " + Tables.USER_PACKETS + " AS tmp"
+ + " WHERE tmp." + UserPackets.MASTER_KEY_ID
+ " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND (" + emailWhere + ")"
+ ")");
@@ -420,7 +422,7 @@ public class KeychainProvider extends ContentProvider {
}
if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = Tables.USER_IDS + "." + UserIds.USER_ID + " ASC";
+ sortOrder = Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC";
}
// uri to watch is all /key_rings/
@@ -430,7 +432,7 @@ public class KeychainProvider extends ContentProvider {
}
case KEY_RING_KEYS: {
- HashMap projectionMap = new HashMap();
+ HashMap projectionMap = new HashMap<>();
projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id");
projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK);
@@ -458,37 +460,45 @@ public class KeychainProvider extends ContentProvider {
case KEY_RINGS_USER_IDS:
case KEY_RING_USER_IDS: {
- HashMap projectionMap = new HashMap();
- projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id");
- projectionMap.put(UserIds.MASTER_KEY_ID, Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID);
- projectionMap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID);
- projectionMap.put(UserIds.RANK, Tables.USER_IDS + "." + UserIds.RANK);
- projectionMap.put(UserIds.IS_PRIMARY, Tables.USER_IDS + "." + UserIds.IS_PRIMARY);
- projectionMap.put(UserIds.IS_REVOKED, Tables.USER_IDS + "." + UserIds.IS_REVOKED);
+ HashMap projectionMap = new HashMap<>();
+ projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id");
+ projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID);
+ projectionMap.put(UserPackets.TYPE, Tables.USER_PACKETS + "." + UserPackets.TYPE);
+ projectionMap.put(UserPackets.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
+ projectionMap.put(UserPackets.ATTRIBUTE_DATA, Tables.USER_PACKETS + "." + UserPackets.ATTRIBUTE_DATA);
+ projectionMap.put(UserPackets.RANK, Tables.USER_PACKETS + "." + UserPackets.RANK);
+ projectionMap.put(UserPackets.IS_PRIMARY, Tables.USER_PACKETS + "." + UserPackets.IS_PRIMARY);
+ projectionMap.put(UserPackets.IS_REVOKED, Tables.USER_PACKETS + "." + UserPackets.IS_REVOKED);
// we take the minimum (>0) here, where "1" is "verified by known secret key"
- projectionMap.put(UserIds.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserIds.VERIFIED);
+ projectionMap.put(UserPackets.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserPackets.VERIFIED);
qb.setProjectionMap(projectionMap);
- qb.setTables(Tables.USER_IDS
+ qb.setTables(Tables.USER_PACKETS
+ " LEFT JOIN " + Tables.CERTS + " ON ("
- + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "
+ + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "
+ Tables.CERTS + "." + Certs.MASTER_KEY_ID
- + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = "
+ + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = "
+ Tables.CERTS + "." + Certs.RANK
+ " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"
+ ")");
- groupBy = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
- + ", " + Tables.USER_IDS + "." + UserIds.RANK;
+ groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK;
+
+ // for now, we only respect user ids here, so TYPE must be NULL
+ // TODO expand with KEY_RING_USER_PACKETS query type which lifts this restriction
+ qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL");
// If we are searching for a particular keyring's ids, add where
if (match == KEY_RING_USER_IDS) {
- qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = ");
+ // TODO remove with the thing above
+ qb.appendWhere(" AND ");
+ qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
}
if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC"
- + "," + Tables.USER_IDS + "." + UserIds.RANK + " ASC";
+ sortOrder = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC"
+ + "," + Tables.USER_PACKETS + "." + UserPackets.RANK + " ASC";
}
break;
@@ -497,7 +507,7 @@ public class KeychainProvider extends ContentProvider {
case KEY_RINGS_PUBLIC:
case KEY_RING_PUBLIC: {
- HashMap projectionMap = new HashMap();
+ HashMap projectionMap = new HashMap<>();
projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id");
projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
@@ -515,7 +525,7 @@ public class KeychainProvider extends ContentProvider {
case KEY_RINGS_SECRET:
case KEY_RING_SECRET: {
- HashMap projectionMap = new HashMap();
+ HashMap projectionMap = new HashMap<>();
projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id");
projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
@@ -533,7 +543,7 @@ public class KeychainProvider extends ContentProvider {
case KEY_RING_CERTS:
case KEY_RING_CERTS_SPECIFIC: {
- HashMap projectionMap = new HashMap();
+ HashMap projectionMap = new HashMap<>();
projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID);
projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID);
projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK);
@@ -542,20 +552,24 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION);
projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER);
projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA);
- projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID);
- projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID);
+ projectionMap.put(Certs.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
+ projectionMap.put(Certs.SIGNER_UID, "signer." + UserPackets.USER_ID + " AS " + Certs.SIGNER_UID);
qb.setProjectionMap(projectionMap);
qb.setTables(Tables.CERTS
- + " JOIN " + Tables.USER_IDS + " ON ("
+ + " JOIN " + Tables.USER_PACKETS + " ON ("
+ Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "
- + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
+ + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ " AND "
+ Tables.CERTS + "." + Certs.RANK + " = "
- + Tables.USER_IDS + "." + UserIds.RANK
- + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON ("
+ + Tables.USER_PACKETS + "." + UserPackets.RANK
+ // for now, we only return user ids here, so TYPE must be NULL
+ // TODO at some point, we should lift this restriction
+ + " AND "
+ + Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"
+ + ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON ("
+ Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = "
- + "signer." + UserIds.MASTER_KEY_ID
+ + "signer." + UserPackets.MASTER_KEY_ID
+ " AND "
+ "signer." + Keys.RANK + " = 0"
+ ")");
@@ -662,8 +676,18 @@ public class KeychainProvider extends ContentProvider {
break;
case KEY_RING_USER_IDS:
- db.insertOrThrow(Tables.USER_IDS, null, values);
- keyId = values.getAsLong(UserIds.MASTER_KEY_ID);
+ // iff TYPE is null, user_id MUST be null as well
+ if ( ! (values.get(UserPacketsColumns.TYPE) == null
+ ? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null)
+ : (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null)
+ )) {
+ throw new AssertionError("Incorrect type for user packet! This is a bug!");
+ }
+ if (values.get(UserPacketsColumns.RANK) == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
+ throw new AssertionError("Rank 0 user packet must be a user id!");
+ }
+ db.insertOrThrow(Tables.USER_PACKETS, null, values);
+ keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
case KEY_RING_CERTS:
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 4f1b4b6c1..a229f454f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -31,6 +31,8 @@ import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
+import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -53,7 +55,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
-import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
@@ -169,7 +170,7 @@ public class ProviderHelper {
Cursor cursor = mContentResolver.query(uri, proj, selection, null, null);
try {
- HashMap result = new HashMap(proj.length);
+ HashMap result = new HashMap<>(proj.length);
if (cursor != null && cursor.moveToFirst()) {
int pos = 0;
for (String p : proj) {
@@ -220,7 +221,7 @@ public class ProviderHelper {
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
try {
- LongSparseArray result = new LongSparseArray();
+ LongSparseArray result = new LongSparseArray<>();
if (cursor != null && cursor.moveToFirst()) do {
long masterKeyId = cursor.getLong(0);
@@ -349,7 +350,7 @@ public class ProviderHelper {
mIndent += 1;
// save all keys and userIds included in keyRing object in database
- operations = new ArrayList();
+ operations = new ArrayList<>();
log(LogType.MSG_IP_INSERT_KEYRING);
{ // insert keyring
@@ -439,18 +440,18 @@ public class ProviderHelper {
// classify and order user ids. primary are moved to the front, revoked to the back,
// otherwise the order in the keyfile is preserved.
+ List uids = new ArrayList<>();
+
if (trustedKeys.size() == 0) {
log(LogType.MSG_IP_UID_CLASSIFYING_ZERO);
} else {
log(LogType.MSG_IP_UID_CLASSIFYING, trustedKeys.size());
}
mIndent += 1;
- List uids = new ArrayList();
- for (byte[] rawUserId : new IterableIterator(
- masterKey.getUnorderedRawUserIds().iterator())) {
+ for (byte[] rawUserId : masterKey.getUnorderedRawUserIds()) {
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
- UserIdItem item = new UserIdItem();
+ UserPacketItem item = new UserPacketItem();
uids.add(item);
item.userId = userId;
@@ -459,7 +460,7 @@ public class ProviderHelper {
log(LogType.MSG_IP_UID_PROCESSING, userId);
mIndent += 1;
// look through signatures for this specific key
- for (WrappedSignature cert : new IterableIterator(
+ for (WrappedSignature cert : new IterableIterator<>(
masterKey.getSignaturesForRawId(rawUserId))) {
long certId = cert.getKeyId();
// self signature
@@ -533,6 +534,105 @@ public class ProviderHelper {
}
mIndent -= 1;
+ ArrayList userAttributes = masterKey.getUnorderedUserAttributes();
+ // Don't spam the log if there aren't even any attributes
+ if ( ! userAttributes.isEmpty()) {
+ log(LogType.MSG_IP_UAT_CLASSIFYING);
+ }
+
+ mIndent += 1;
+ for (WrappedUserAttribute userAttribute : userAttributes) {
+
+ UserPacketItem item = new UserPacketItem();
+ uids.add(item);
+ item.type = userAttribute.getType();
+ item.attributeData = userAttribute.getEncoded();
+
+ int unknownCerts = 0;
+
+ switch (item.type) {
+ case WrappedUserAttribute.UAT_IMAGE:
+ log(LogType.MSG_IP_UAT_PROCESSING_IMAGE);
+ break;
+ default:
+ log(LogType.MSG_IP_UAT_PROCESSING_UNKNOWN);
+ break;
+ }
+ mIndent += 1;
+ // look through signatures for this specific key
+ for (WrappedSignature cert : new IterableIterator<>(
+ masterKey.getSignaturesForUserAttribute(userAttribute))) {
+ long certId = cert.getKeyId();
+ // self signature
+ if (certId == masterKeyId) {
+
+ // NOTE self-certificates are already verified during canonicalization,
+ // AND we know there is at most one cert plus at most one revocation
+ if (!cert.isRevocation()) {
+ item.selfCert = cert;
+ } else {
+ item.isRevoked = true;
+ log(LogType.MSG_IP_UAT_REVOKED);
+ }
+ continue;
+
+ }
+
+ // do we have a trusted key for this?
+ if (trustedKeys.indexOfKey(certId) < 0) {
+ unknownCerts += 1;
+ continue;
+ }
+
+ // verify signatures from known private keys
+ CanonicalizedPublicKey trustedKey = trustedKeys.get(certId);
+
+ try {
+ cert.init(trustedKey);
+ // if it doesn't certify, leave a note and skip
+ if ( ! cert.verifySignature(masterKey, userAttribute)) {
+ log(LogType.MSG_IP_UAT_CERT_BAD);
+ continue;
+ }
+
+ log(cert.isRevocation()
+ ? LogType.MSG_IP_UAT_CERT_GOOD_REVOKE
+ : LogType.MSG_IP_UAT_CERT_GOOD,
+ KeyFormattingUtils.convertKeyIdToHexShort(trustedKey.getKeyId())
+ );
+
+ // check if there is a previous certificate
+ WrappedSignature prev = item.trustedCerts.get(cert.getKeyId());
+ if (prev != null) {
+ // if it's newer, skip this one
+ if (prev.getCreationTime().after(cert.getCreationTime())) {
+ log(LogType.MSG_IP_UAT_CERT_OLD);
+ continue;
+ }
+ // if the previous one was a non-revokable certification, no need to look further
+ if (!prev.isRevocation() && !prev.isRevokable()) {
+ log(LogType.MSG_IP_UAT_CERT_NONREVOKE);
+ continue;
+ }
+ log(LogType.MSG_IP_UAT_CERT_NEW);
+ }
+ item.trustedCerts.put(cert.getKeyId(), cert);
+
+ } catch (PgpGeneralException e) {
+ log(LogType.MSG_IP_UAT_CERT_ERROR,
+ KeyFormattingUtils.convertKeyIdToHex(cert.getKeyId()));
+ }
+
+ }
+
+ if (unknownCerts > 0) {
+ log(LogType.MSG_IP_UAT_CERTS_UNKNOWN, unknownCerts);
+ }
+ mIndent -= 1;
+
+ }
+ mIndent -= 1;
+
progress.setProgress(LogType.MSG_IP_UID_REORDER.getMsgId(), 65, 100);
log(LogType.MSG_IP_UID_REORDER);
// primary before regular before revoked (see UserIdItem.compareTo)
@@ -540,7 +640,7 @@ public class ProviderHelper {
Collections.sort(uids);
// iterate and put into db
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
- UserIdItem item = uids.get(userIdRank);
+ UserPacketItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
if (item.selfCert != null) {
// TODO get rid of "self verified" status? this cannot even happen anymore!
@@ -605,23 +705,31 @@ public class ProviderHelper {
}
- private static class UserIdItem implements Comparable {
+ private static class UserPacketItem implements Comparable {
+ Integer type;
String userId;
+ byte[] attributeData;
boolean isPrimary = false;
boolean isRevoked = false;
WrappedSignature selfCert;
- LongSparseArray trustedCerts = new LongSparseArray();
+ LongSparseArray trustedCerts = new LongSparseArray<>();
@Override
- public int compareTo(UserIdItem o) {
- // if one key is primary but the other isn't, the primary one always comes first
- if (isPrimary != o.isPrimary) {
- return isPrimary ? -1 : 1;
- }
+ public int compareTo(UserPacketItem o) {
// revoked keys always come last!
if (isRevoked != o.isRevoked) {
return isRevoked ? 1 : -1;
}
+ // if one is a user id, but the other isn't, the user id always comes first.
+ // we compare for null values here, so != is the correct operator!
+ // noinspection NumberEquality
+ if (type != o.type) {
+ return type == null ? -1 : 1;
+ }
+ // if one key is primary but the other isn't, the primary one always comes first
+ if (isPrimary != o.isPrimary) {
+ return isPrimary ? -1 : 1;
+ }
return 0;
}
}
@@ -967,7 +1075,7 @@ public class ProviderHelper {
// No keys existing might be a legitimate option, we write an empty file in that case
cursor.moveToFirst();
ParcelableFileCache cache =
- new ParcelableFileCache(mContext, "consolidate_secret.pcl");
+ new ParcelableFileCache<>(mContext, "consolidate_secret.pcl");
cache.writeCache(cursor.getCount(), new Iterator() {
ParcelableKeyRing ring;
@@ -1029,7 +1137,7 @@ public class ProviderHelper {
// No keys existing might be a legitimate option, we write an empty file in that case
cursor.moveToFirst();
ParcelableFileCache cache =
- new ParcelableFileCache(mContext, "consolidate_public.pcl");
+ new ParcelableFileCache<>(mContext, "consolidate_public.pcl");
cache.writeCache(cursor.getCount(), new Iterator() {
ParcelableKeyRing ring;
@@ -1112,9 +1220,9 @@ public class ProviderHelper {
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
ParcelableFileCache cacheSecret =
- new ParcelableFileCache(mContext, "consolidate_secret.pcl");
+ new ParcelableFileCache<>(mContext, "consolidate_secret.pcl");
ParcelableFileCache cachePublic =
- new ParcelableFileCache(mContext, "consolidate_public.pcl");
+ new ParcelableFileCache<>(mContext, "consolidate_public.pcl");
// Set flag that we have a cached consolidation here
try {
@@ -1234,15 +1342,17 @@ public class ProviderHelper {
* Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing
*/
private ContentProviderOperation
- buildUserIdOperations(long masterKeyId, UserIdItem item, int rank) {
+ buildUserIdOperations(long masterKeyId, UserPacketItem item, int rank) {
ContentValues values = new ContentValues();
- values.put(UserIds.MASTER_KEY_ID, masterKeyId);
- values.put(UserIds.USER_ID, item.userId);
- values.put(UserIds.IS_PRIMARY, item.isPrimary);
- values.put(UserIds.IS_REVOKED, item.isRevoked);
- values.put(UserIds.RANK, rank);
+ values.put(UserPackets.MASTER_KEY_ID, masterKeyId);
+ values.put(UserPackets.TYPE, item.type);
+ values.put(UserPackets.USER_ID, item.userId);
+ values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
+ values.put(UserPackets.IS_PRIMARY, item.isPrimary);
+ values.put(UserPackets.IS_REVOKED, item.isRevoked);
+ values.put(UserPackets.RANK, rank);
- Uri uri = UserIds.buildUserIdsUri(masterKeyId);
+ Uri uri = UserPackets.buildUserIdsUri(masterKeyId);
return ContentProviderOperation.newInsert(uri).withValues(values).build();
}
@@ -1270,7 +1380,7 @@ public class ProviderHelper {
public ArrayList getRegisteredApiApps() {
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
- ArrayList packageNames = new ArrayList();
+ ArrayList packageNames = new ArrayList<>();
try {
if (cursor != null) {
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
@@ -1375,7 +1485,7 @@ public class ProviderHelper {
}
public Set getAllKeyIdsForApp(Uri uri) {
- Set keyIds = new HashSet();
+ Set keyIds = new HashSet<>();
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
try {
@@ -1395,7 +1505,7 @@ public class ProviderHelper {
}
public Set getAllFingerprints(Uri uri) {
- Set fingerprints = new HashSet();
+ Set fingerprints = new HashSet<>();
String[] projection = new String[]{KeyRings.FINGERPRINT};
Cursor cursor = mContentResolver.query(uri, projection, null, null, null);
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index 478013f55..f2af43b6f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -85,9 +85,9 @@ public class OpenPgpService extends RemoteService {
boolean missingUserIdsCheck = false;
boolean duplicateUserIdsCheck = false;
- ArrayList keyIds = new ArrayList();
- ArrayList missingUserIds = new ArrayList();
- ArrayList duplicateUserIds = new ArrayList();
+ ArrayList keyIds = new ArrayList<>();
+ ArrayList missingUserIds = new ArrayList<>();
+ ArrayList duplicateUserIds = new ArrayList<>();
if (!noUserIdsCheck) {
for (String email : encryptionUserIds) {
// try to find the key for this specific email
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
index e71b52ccd..672f59285 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/RemoteService.java
@@ -27,7 +27,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.Binder;
-import android.text.TextUtils;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
@@ -216,9 +215,7 @@ public abstract class RemoteService extends Service {
String[] callingPackages = getPackageManager().getPackagesForUid(uid);
// is calling package allowed to use this service?
- for (int i = 0; i < callingPackages.length; i++) {
- String currentPkg = callingPackages[i];
-
+ for (String currentPkg : callingPackages) {
if (isPackageAllowed(currentPkg)) {
return true;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
index 0e9678980..e5edd6a0f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
@@ -20,14 +20,13 @@ package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
+import org.sufficientlysecure.keychain.ui.BaseActivity;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
@@ -35,7 +34,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.util.Log;
-public class AccountSettingsActivity extends ActionBarActivity {
+public class AccountSettingsActivity extends BaseActivity {
private Uri mAccountUri;
private AccountSettingsFragment mAccountSettingsFragment;
@@ -45,17 +44,20 @@ public class AccountSettingsActivity extends ActionBarActivity {
super.onCreate(savedInstanceState);
// Inflate a "Done" custom action bar
- ActionBarHelper.setOneButtonView(getSupportActionBar(),
- R.string.api_settings_save, R.drawable.ic_action_done,
+ setFullScreenDialogDoneClose(R.string.api_settings_save,
new View.OnClickListener() {
@Override
public void onClick(View v) {
- // "Done"
save();
}
+ },
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
});
- setContentView(R.layout.api_account_settings_activity);
mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_account_settings_fragment);
@@ -72,6 +74,11 @@ public class AccountSettingsActivity extends ActionBarActivity {
}
}
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.api_account_settings_activity);
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -125,9 +132,4 @@ public class AccountSettingsActivity extends ActionBarActivity {
}
}
- @Override
- public void onBackPressed() {
- save();
- super.onBackPressed();
- }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
index 56e3b22e2..e91482e28 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
@@ -22,8 +22,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
@@ -33,9 +31,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.ui.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
-public class AppSettingsActivity extends ActionBarActivity {
+public class AppSettingsActivity extends BaseActivity {
private Uri mAppUri;
private AppSettingsFragment mSettingsFragment;
@@ -49,12 +48,11 @@ public class AppSettingsActivity extends ActionBarActivity {
super.onCreate(savedInstanceState);
// let the actionbar look like Android's contact app
- ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setIcon(android.R.color.transparent);
- actionBar.setHomeButtonEnabled(true);
+// ActionBar actionBar = getSupportActionBar();
+// actionBar.setDisplayHomeAsUpEnabled(true);
+// actionBar.setIcon(android.R.color.transparent);
+// actionBar.setHomeButtonEnabled(true);
- setContentView(R.layout.api_app_settings_activity);
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment);
@@ -71,6 +69,11 @@ public class AppSettingsActivity extends ActionBarActivity {
}
}
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.api_app_settings_activity);
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java
index 11b0deb33..3b4cc654e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java
@@ -21,16 +21,27 @@ import android.os.Bundle;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.DrawerActivity;
+import org.sufficientlysecure.keychain.ui.NavDrawerActivity;
-public class AppsListActivity extends DrawerActivity {
+public class AppsListActivity extends NavDrawerActivity {
+
+// @Override
+// protected void onCreate(Bundle savedInstanceState) {
+// super.onCreate(savedInstanceState);
+//
+// activateDrawerNavigation(savedInstanceState);
+// }
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
+ public void init(Bundle savedInstanceState) {
+ super.init(savedInstanceState);
setContentView(R.layout.api_apps_list_activity);
-
- activateDrawerNavigation(savedInstanceState);
}
+
+// @Override
+// protected void initLayout() {
+// setContentView(R.layout.api_apps_list_activity);
+// }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
index d7b723eb0..cbc593b0a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
@@ -22,7 +22,6 @@ import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v7.app.ActionBarActivity;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -39,14 +38,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
+import org.sufficientlysecure.keychain.ui.BaseActivity;
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
-import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
-public class RemoteServiceActivity extends ActionBarActivity {
+public class RemoteServiceActivity extends BaseActivity {
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX
@@ -96,235 +95,250 @@ public class RemoteServiceActivity extends ActionBarActivity {
handleActions(getIntent(), savedInstanceState);
}
+ @Override
+ protected void initLayout() {
+ // done in handleActions()
+ }
+
protected void handleActions(Intent intent, Bundle savedInstanceState) {
String action = intent.getAction();
final Bundle extras = intent.getExtras();
- if (ACTION_REGISTER.equals(action)) {
- final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
- final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
- Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
+ switch (action) {
+ case ACTION_REGISTER: {
+ final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+ final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
+ Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
- setContentView(R.layout.api_remote_register_app);
+ setContentView(R.layout.api_remote_register_app);
+ initToolbar();
- mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
- R.id.api_app_settings_fragment);
+ mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_app_settings_fragment);
- AppSettings settings = new AppSettings(packageName, packageSignature);
- mAppSettingsFragment.setAppSettings(settings);
+ AppSettings settings = new AppSettings(packageName, packageSignature);
+ mAppSettingsFragment.setAppSettings(settings);
- // Inflate a "Done"/"Cancel" custom action bar view
- ActionBarHelper.setTwoButtonView(getSupportActionBar(),
- R.string.api_register_allow, R.drawable.ic_action_done,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Allow
+ // Inflate a "Done"/"Cancel" custom action bar view
+ setFullScreenDialogTwoButtons(
+ R.string.api_register_allow, R.drawable.ic_check_white_24dp,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Allow
- mProviderHelper.insertApiApp(mAppSettingsFragment.getAppSettings());
-
- // give data through for new service call
- Intent resultData = extras.getParcelable(EXTRA_DATA);
- RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
- RemoteServiceActivity.this.finish();
- }
- }, R.string.api_register_disallow, R.drawable.ic_action_cancel,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Disallow
- RemoteServiceActivity.this.setResult(RESULT_CANCELED);
- RemoteServiceActivity.this.finish();
- }
- }
- );
- } else if (ACTION_CREATE_ACCOUNT.equals(action)) {
- final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
- final String accName = extras.getString(EXTRA_ACC_NAME);
-
- setContentView(R.layout.api_remote_create_account);
-
- mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
- R.id.api_account_settings_fragment);
-
- TextView text = (TextView) findViewById(R.id.api_remote_create_account_text);
-
- // update existing?
- Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName);
- AccountSettings settings = mProviderHelper.getApiAccountSettings(uri);
- if (settings == null) {
- // create new account
- settings = new AccountSettings(accName);
- mUpdateExistingAccount = false;
-
- text.setText(R.string.api_create_account_text);
- } else {
- // update existing account
- mUpdateExistingAccount = true;
-
- text.setText(R.string.api_update_account_text);
- }
- mAccSettingsFragment.setAccSettings(settings);
-
- // Inflate a "Done"/"Cancel" custom action bar view
- ActionBarHelper.setTwoButtonView(getSupportActionBar(),
- R.string.api_settings_save, R.drawable.ic_action_done,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Save
-
- // user needs to select a key if it has explicitly requested (None is only allowed for new accounts)
- if (mUpdateExistingAccount && mAccSettingsFragment.getAccSettings().getKeyId() == Constants.key.none) {
- Notify.showNotify(RemoteServiceActivity.this, getString(R.string.api_register_error_select_key), Notify.Style.ERROR);
- } else {
- if (mUpdateExistingAccount) {
- Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);
- Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build();
- mProviderHelper.updateApiAccount(
- accountUri,
- mAccSettingsFragment.getAccSettings());
- } else {
- mProviderHelper.insertApiAccount(
- KeychainContract.ApiAccounts.buildBaseUri(packageName),
- mAccSettingsFragment.getAccSettings());
- }
+ mProviderHelper.insertApiApp(mAppSettingsFragment.getAppSettings());
// give data through for new service call
Intent resultData = extras.getParcelable(EXTRA_DATA);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish();
}
+ }, R.string.api_register_disallow, R.drawable.ic_close_white_24dp,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Disallow
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
}
- }, R.string.api_settings_cancel, R.drawable.ic_action_cancel,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Cancel
- RemoteServiceActivity.this.setResult(RESULT_CANCELED);
- RemoteServiceActivity.this.finish();
- }
- }
- );
-
- } else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
- long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
- boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
- ArrayList missingUserIds = intent
- .getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
- ArrayList dublicateUserIds = intent
- .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS);
-
- SpannableStringBuilder ssb = new SpannableStringBuilder();
- final SpannableString textIntro = new SpannableString(
- noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids)
- : getString(R.string.api_select_pub_keys_text)
- );
- textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- ssb.append(textIntro);
-
- if (missingUserIds != null && missingUserIds.size() > 0) {
- ssb.append("\n\n");
- ssb.append(getString(R.string.api_select_pub_keys_missing_text));
- ssb.append("\n");
- for (String userId : missingUserIds) {
- SpannableString ss = new SpannableString(userId + "\n");
- ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- ssb.append(ss);
- }
+ );
+ break;
}
- if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
- ssb.append("\n\n");
- ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
- ssb.append("\n");
- for (String userId : dublicateUserIds) {
- SpannableString ss = new SpannableString(userId + "\n");
- ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- ssb.append(ss);
+ case ACTION_CREATE_ACCOUNT: {
+ final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+ final String accName = extras.getString(EXTRA_ACC_NAME);
+
+ setContentView(R.layout.api_remote_create_account);
+ initToolbar();
+
+ mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_account_settings_fragment);
+
+ TextView text = (TextView) findViewById(R.id.api_remote_create_account_text);
+
+ // update existing?
+ Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName);
+ AccountSettings settings = mProviderHelper.getApiAccountSettings(uri);
+ if (settings == null) {
+ // create new account
+ settings = new AccountSettings(accName);
+ mUpdateExistingAccount = false;
+
+ text.setText(R.string.api_create_account_text);
+ } else {
+ // update existing account
+ mUpdateExistingAccount = true;
+
+ text.setText(R.string.api_update_account_text);
}
+ mAccSettingsFragment.setAccSettings(settings);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ setFullScreenDialogDoneClose(R.string.api_settings_save,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Save
+
+ // user needs to select a key if it has explicitly requested (None is only allowed for new accounts)
+ if (mUpdateExistingAccount && mAccSettingsFragment.getAccSettings().getKeyId() == Constants.key.none) {
+ Notify.showNotify(RemoteServiceActivity.this, getString(R.string.api_register_error_select_key), Notify.Style.ERROR);
+ } else {
+ if (mUpdateExistingAccount) {
+ Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);
+ Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build();
+ mProviderHelper.updateApiAccount(
+ accountUri,
+ mAccSettingsFragment.getAccSettings());
+ } else {
+ mProviderHelper.insertApiAccount(
+ KeychainContract.ApiAccounts.buildBaseUri(packageName),
+ mAccSettingsFragment.getAccSettings());
+ }
+
+ // give data through for new service call
+ Intent resultData = extras.getParcelable(EXTRA_DATA);
+ RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
+ RemoteServiceActivity.this.finish();
+ }
+ }
+ },
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Cancel
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ });
+
+ break;
}
+ case ACTION_SELECT_PUB_KEYS: {
+ long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
+ boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
+ ArrayList missingUserIds = intent
+ .getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
+ ArrayList dublicateUserIds = intent
+ .getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS);
- // Inflate a "Done"/"Cancel" custom action bar view
- ActionBarHelper.setTwoButtonView(getSupportActionBar(),
- R.string.btn_okay, R.drawable.ic_action_done,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // add key ids to params Bundle for new request
- Intent resultData = extras.getParcelable(EXTRA_DATA);
- resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS,
- mSelectFragment.getSelectedMasterKeyIds());
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ final SpannableString textIntro = new SpannableString(
+ noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids)
+ : getString(R.string.api_select_pub_keys_text)
+ );
+ textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ssb.append(textIntro);
- RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
- RemoteServiceActivity.this.finish();
- }
- }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // cancel
- RemoteServiceActivity.this.setResult(RESULT_CANCELED);
- RemoteServiceActivity.this.finish();
- }
+ if (missingUserIds != null && missingUserIds.size() > 0) {
+ ssb.append("\n\n");
+ ssb.append(getString(R.string.api_select_pub_keys_missing_text));
+ ssb.append("\n");
+ for (String userId : missingUserIds) {
+ SpannableString ss = new SpannableString(userId + "\n");
+ ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ssb.append(ss);
+ }
+ }
+ if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
+ ssb.append("\n\n");
+ ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
+ ssb.append("\n");
+ for (String userId : dublicateUserIds) {
+ SpannableString ss = new SpannableString(userId + "\n");
+ ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ ssb.append(ss);
}
- );
-
- setContentView(R.layout.api_remote_select_pub_keys);
-
- // set text on view
- TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text);
- textView.setText(ssb, TextView.BufferType.SPANNABLE);
-
- /* Load select pub keys fragment */
- // Check that the activity is using the layout version with
- // the fragment_container FrameLayout
- if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) {
-
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (savedInstanceState != null) {
- return;
}
- // Create an instance of the fragment
- mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
+ setContentView(R.layout.api_remote_select_pub_keys);
+ initToolbar();
- // Add the fragment to the 'fragment_container' FrameLayout
- getSupportFragmentManager().beginTransaction()
- .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit();
- }
- } else if (ACTION_ERROR_MESSAGE.equals(action)) {
- String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
+ // Inflate a "Done"/"Cancel" custom action bar view
+ setFullScreenDialogDoneClose(R.string.btn_okay,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // add key ids to params Bundle for new request
+ Intent resultData = extras.getParcelable(EXTRA_DATA);
+ resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS,
+ mSelectFragment.getSelectedMasterKeyIds());
- Spannable redErrorMessage = new SpannableString(errorMessage);
- redErrorMessage.setSpan(new ForegroundColorSpan(Color.RED), 0, errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
+ RemoteServiceActivity.this.finish();
+ }
+ },
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // cancel
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ });
- // Inflate a "Done" custom action bar view
- ActionBarHelper.setOneButtonView(getSupportActionBar(),
- R.string.btn_okay, R.drawable.ic_action_done,
- new View.OnClickListener() {
+ // set text on view
+ TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text);
+ textView.setText(ssb, TextView.BufferType.SPANNABLE);
- @Override
- public void onClick(View v) {
- RemoteServiceActivity.this.setResult(RESULT_CANCELED);
- RemoteServiceActivity.this.finish();
- }
+ /* Load select pub keys fragment */
+ // Check that the activity is using the layout version with
+ // the fragment_container FrameLayout
+ if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) {
+
+ // However, if we're being restored from a previous state,
+ // then we don't need to do anything and should return or else
+ // we could end up with overlapping fragments.
+ if (savedInstanceState != null) {
+ return;
}
- );
- setContentView(R.layout.api_remote_error_message);
+ // Create an instance of the fragment
+ mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
- // set text on view
- TextView textView = (TextView) findViewById(R.id.api_app_error_message_text);
- textView.setText(redErrorMessage);
- } else {
- Log.e(Constants.TAG, "Action does not exist!");
- setResult(RESULT_CANCELED);
- finish();
+ // Add the fragment to the 'fragment_container' FrameLayout
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit();
+ }
+ break;
+ }
+ case ACTION_ERROR_MESSAGE: {
+ String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
+
+ Spannable redErrorMessage = new SpannableString(errorMessage);
+ redErrorMessage.setSpan(new ForegroundColorSpan(Color.RED), 0, errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ setContentView(R.layout.api_remote_error_message);
+ initToolbar();
+
+ // Inflate a "Done" custom action bar view
+ setFullScreenDialogClose(
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ RemoteServiceActivity.this.setResult(RESULT_CANCELED);
+ RemoteServiceActivity.this.finish();
+ }
+ }
+ );
+
+ // set text on view
+ TextView textView = (TextView) findViewById(R.id.api_app_error_message_text);
+ textView.setText(redErrorMessage);
+ break;
+ }
+ default:
+ Log.e(Constants.TAG, "Action does not exist!");
+ setResult(RESULT_CANCELED);
+ finish();
+ break;
}
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
index dd9c0d769..f0dbf0820 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
@@ -34,7 +34,7 @@ public class CertifyActionsParcel implements Parcelable {
final public long mMasterKeyId;
public CertifyLevel mLevel;
- public ArrayList mCertifyActions = new ArrayList();
+ public ArrayList mCertifyActions = new ArrayList<>();
public CertifyActionsParcel(long masterKeyId) {
mMasterKeyId = masterKeyId;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
index 2dc057941..bcb5da277 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -30,9 +30,11 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.DeleteOperation;
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
+import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.util.FileHelper;
@@ -90,6 +92,8 @@ public class KeychainIntentService extends IntentService implements Progressable
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
+ public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
+
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
@@ -160,6 +164,10 @@ public class KeychainIntentService extends IntentService implements Progressable
// certify key
public static final String CERTIFY_PARCEL = "certify_parcel";
+ // promote key
+ public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
+ public static final String PROMOTE_TYPE = "promote_type";
+
// consolidate
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
@@ -223,301 +231,326 @@ public class KeychainIntentService extends IntentService implements Progressable
String action = intent.getAction();
// executeServiceMethod action from extra bundle
- if (ACTION_CERTIFY_KEYRING.equals(action)) {
-
- // Input
- CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
- String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
-
- // Operation
- CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
- CertifyResult result = op.certify(parcel, keyServerUri);
-
- // Result
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
-
- } else if (ACTION_CONSOLIDATE.equals(action)) {
-
- // Operation
- ConsolidateResult result;
- if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
- result = new ProviderHelper(this).consolidateDatabaseStep2(this);
- } else {
- result = new ProviderHelper(this).consolidateDatabaseStep1(this);
- }
-
- // Result
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
-
- } else if (ACTION_DECRYPT_METADATA.equals(action)) {
-
- try {
- /* Input */
- String passphrase = data.getString(DECRYPT_PASSPHRASE);
- byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
-
- InputData inputData = createDecryptInputData(data);
-
- /* Operation */
-
- Bundle resultData = new Bundle();
-
- // verifyText and decrypt returning additional resultData values for the
- // verification of signatures
- PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
- this, new ProviderHelper(this), this, inputData, null
- );
- builder.setAllowSymmetricDecryption(true)
- .setPassphrase(passphrase)
- .setDecryptMetadataOnly(true)
- .setNfcState(nfcDecryptedSessionKey);
-
- DecryptVerifyResult decryptVerifyResult = builder.build().execute();
-
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult);
- } catch (Exception e) {
- sendErrorToHandler(e);
- }
-
- } else if (ACTION_DECRYPT_VERIFY.equals(action)) {
-
- try {
- /* Input */
- String passphrase = data.getString(DECRYPT_PASSPHRASE);
- byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
-
- InputData inputData = createDecryptInputData(data);
- OutputStream outStream = createCryptOutputStream(data);
-
- /* Operation */
-
- Bundle resultData = new Bundle();
-
- // verifyText and decrypt returning additional resultData values for the
- // verification of signatures
- PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
- this, new ProviderHelper(this), this,
- inputData, outStream
- );
- builder.setAllowSymmetricDecryption(true)
- .setPassphrase(passphrase)
- .setNfcState(nfcDecryptedSessionKey);
-
- DecryptVerifyResult decryptVerifyResult = builder.build().execute();
-
- outStream.close();
-
- resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
-
- /* Output */
-
- finalizeDecryptOutputStream(data, resultData, outStream);
-
- Log.logDebugBundle(resultData, "resultData");
-
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
- } catch (Exception e) {
- sendErrorToHandler(e);
- }
-
- } else if (ACTION_DELETE.equals(action)) {
-
- // Input
- long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
- boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
-
- // Operation
- DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
- DeleteResult result = op.execute(masterKeyIds, isSecret);
-
- // Result
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
-
- } else if (ACTION_EDIT_KEYRING.equals(action)) {
-
- // Input
- SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
- String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE);
-
- // Operation
- EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
- EditKeyResult result = op.execute(saveParcel, passphrase);
-
- // Result
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
-
- } else if (ACTION_EXPORT_KEYRING.equals(action)) {
-
- // Input
- boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
- String outputFile = data.getString(EXPORT_FILENAME);
- Uri outputUri = data.getParcelable(EXPORT_URI);
-
- boolean exportAll = data.getBoolean(EXPORT_ALL);
- long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
-
- // Operation
- ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
- ExportResult result;
- if (outputFile != null) {
- result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
- } else {
- result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
- }
-
- // Result
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
-
- } else if (ACTION_IMPORT_KEYRING.equals(action)) {
-
- try {
+ switch (action) {
+ case ACTION_CERTIFY_KEYRING: {
// Input
- String keyServer = data.getString(IMPORT_KEY_SERVER);
- Iterator entries;
- int numEntries;
- if (data.containsKey(IMPORT_KEY_LIST)) {
- // get entries from intent
- ArrayList list = data.getParcelableArrayList(IMPORT_KEY_LIST);
- entries = list.iterator();
- numEntries = list.size();
- } else {
- // get entries from cached file
- ParcelableFileCache cache =
- new ParcelableFileCache(this, "key_import.pcl");
- IteratorWithSize it = cache.readCache();
- entries = it;
- numEntries = it.getSize();
- }
+ CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
+ String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
// Operation
- ImportExportOperation importExportOperation = new ImportExportOperation(
- this, providerHelper, this, mActionCanceled);
- ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries, keyServer);
-
- // Special: consolidate on secret key import (cannot be cancelled!)
- if (result.mSecret > 0) {
- // TODO move this into the import operation
- providerHelper.consolidateDatabaseStep1(this);
- }
-
- // Special: make sure new data is synced into contacts
- ContactSyncAdapterService.requestSync();
+ CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
+ CertifyResult result = op.certify(parcel, keyServerUri);
// Result
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
- } catch (Exception e) {
- sendErrorToHandler(e);
+
+ break;
}
+ case ACTION_CONSOLIDATE: {
- } else if (ACTION_SIGN_ENCRYPT.equals(action)) {
+ // Operation
+ ConsolidateResult result;
+ if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
+ result = new ProviderHelper(this).consolidateDatabaseStep2(this);
+ } else {
+ result = new ProviderHelper(this).consolidateDatabaseStep1(this);
+ }
- try {
+ // Result
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
+
+ break;
+ }
+ case ACTION_DECRYPT_METADATA:
+
+ try {
/* Input */
- int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
- Bundle resultData = new Bundle();
+ String passphrase = data.getString(DECRYPT_PASSPHRASE);
+ byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
- long sigMasterKeyId = data.getLong(ENCRYPT_SIGNATURE_MASTER_ID);
- String sigKeyPassphrase = data.getString(ENCRYPT_SIGNATURE_KEY_PASSPHRASE);
+ InputData inputData = createDecryptInputData(data);
- byte[] nfcHash = data.getByteArray(ENCRYPT_SIGNATURE_NFC_HASH);
- Date nfcTimestamp = (Date) data.getSerializable(ENCRYPT_SIGNATURE_NFC_TIMESTAMP);
+ /* Operation */
- String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
+ Bundle resultData = new Bundle();
- boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
- long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
- int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
- int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1;
- for (int i = 0; i < urisCount; i++) {
- data.putInt(SELECTED_URI, i);
- InputData inputData = createEncryptInputData(data);
- OutputStream outStream = createCryptOutputStream(data);
- String originalFilename = getOriginalFilename(data);
-
- /* Operation */
- PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
- this, new ProviderHelper(this), this, inputData, outStream
+ // verifyText and decrypt returning additional resultData values for the
+ // verification of signatures
+ PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
+ this, new ProviderHelper(this), this, inputData, null
);
- builder.setEnableAsciiArmorOutput(useAsciiArmor)
- .setVersionHeader(PgpHelper.getVersionForHeader(this))
- .setCompressionId(compressionId)
- .setSymmetricEncryptionAlgorithm(
- Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
- .setEncryptionMasterKeyIds(encryptionKeyIds)
- .setSymmetricPassphrase(symmetricPassphrase)
- .setOriginalFilename(originalFilename);
+ builder.setAllowSymmetricDecryption(true)
+ .setPassphrase(passphrase)
+ .setDecryptMetadataOnly(true)
+ .setNfcState(nfcDecryptedSessionKey);
- try {
+ DecryptVerifyResult decryptVerifyResult = builder.build().execute();
- // Find the appropriate subkey to sign with
- CachedPublicKeyRing signingRing =
- new ProviderHelper(this).getCachedPublicKeyRing(sigMasterKeyId);
- long sigSubKeyId = signingRing.getSecretSignId();
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
- // Set signature settings
- builder.setSignatureMasterKeyId(sigMasterKeyId)
- .setSignatureSubKeyId(sigSubKeyId)
- .setSignaturePassphrase(sigKeyPassphrase)
- .setSignatureHashAlgorithm(
- Preferences.getPreferences(this).getDefaultHashAlgorithm())
- .setAdditionalEncryptId(sigMasterKeyId);
- if (nfcHash != null && nfcTimestamp != null) {
- builder.setNfcState(nfcHash, nfcTimestamp);
- }
+ break;
+ case ACTION_DECRYPT_VERIFY:
- } catch (PgpKeyNotFoundException e) {
- // encrypt-only
- // TODO Just silently drop the requested signature? Shouldn't we throw here?
- }
+ try {
+ /* Input */
+ String passphrase = data.getString(DECRYPT_PASSPHRASE);
+ byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
- // this assumes that the bytes are cleartext (valid for current implementation!)
- if (source == IO_BYTES) {
- builder.setCleartextSignature(true);
- }
+ InputData inputData = createDecryptInputData(data);
+ OutputStream outStream = createCryptOutputStream(data);
- SignEncryptResult result = builder.build().execute();
- resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result);
+ /* Operation */
+
+ Bundle resultData = new Bundle();
+
+ // verifyText and decrypt returning additional resultData values for the
+ // verification of signatures
+ PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
+ this, new ProviderHelper(this), this,
+ inputData, outStream
+ );
+ builder.setAllowSymmetricDecryption(true)
+ .setPassphrase(passphrase)
+ .setNfcState(nfcDecryptedSessionKey);
+
+ DecryptVerifyResult decryptVerifyResult = builder.build().execute();
outStream.close();
- /* Output */
+ resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
- finalizeEncryptOutputStream(data, resultData, outStream);
+ /* Output */
+ finalizeDecryptOutputStream(data, resultData, outStream);
+
+ Log.logDebugBundle(resultData, "resultData");
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
}
- Log.logDebugBundle(resultData, "resultData");
+ break;
+ case ACTION_DELETE: {
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
- } catch (Exception e) {
- sendErrorToHandler(e);
+ // Input
+ long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
+ boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
+
+ // Operation
+ DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
+ DeleteResult result = op.execute(masterKeyIds, isSecret);
+
+ // Result
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
+
+ break;
}
+ case ACTION_EDIT_KEYRING: {
- } else if (ACTION_UPLOAD_KEYRING.equals(action)) {
+ // Input
+ SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
+ String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE);
- try {
+ // Operation
+ EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
+ EditKeyResult result = op.execute(saveParcel, passphrase);
- /* Input */
- String keyServer = data.getString(UPLOAD_KEY_SERVER);
- // and dataUri!
+ // Result
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
- /* Operation */
- HkpKeyserver server = new HkpKeyserver(keyServer);
+ break;
+ }
+ case ACTION_PROMOTE_KEYRING: {
- CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
+ // Input
+ long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID);
+
+ // Operation
+ PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);
+ PromoteKeyResult result = op.execute(keyRingId);
+
+ // Result
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
+
+ break;
+ }
+ case ACTION_EXPORT_KEYRING: {
+
+ // Input
+ boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
+ String outputFile = data.getString(EXPORT_FILENAME);
+ Uri outputUri = data.getParcelable(EXPORT_URI);
+
+ boolean exportAll = data.getBoolean(EXPORT_ALL);
+ long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
+
+ // Operation
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
+ ExportResult result;
+ if (outputFile != null) {
+ result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
+ } else {
+ result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
+ }
+
+ // Result
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
+
+ break;
+ }
+ case ACTION_IMPORT_KEYRING:
try {
- importExportOperation.uploadKeyRingToServer(server, keyring);
- } catch (Keyserver.AddKeyException e) {
- throw new PgpGeneralException("Unable to export key to selected server");
+
+ // Input
+ String keyServer = data.getString(IMPORT_KEY_SERVER);
+ Iterator entries;
+ int numEntries;
+ if (data.containsKey(IMPORT_KEY_LIST)) {
+ // get entries from intent
+ ArrayList list = data.getParcelableArrayList(IMPORT_KEY_LIST);
+ entries = list.iterator();
+ numEntries = list.size();
+ } else {
+ // get entries from cached file
+ ParcelableFileCache cache =
+ new ParcelableFileCache<>(this, "key_import.pcl");
+ IteratorWithSize it = cache.readCache();
+ entries = it;
+ numEntries = it.getSize();
+ }
+
+ // Operation
+ ImportExportOperation importExportOperation = new ImportExportOperation(
+ this, providerHelper, this, mActionCanceled);
+ ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries, keyServer);
+
+ // Special: consolidate on secret key import (cannot be cancelled!)
+ if (result.mSecret > 0) {
+ // TODO move this into the import operation
+ providerHelper.consolidateDatabaseStep1(this);
+ }
+
+ // Special: make sure new data is synced into contacts
+ ContactSyncAdapterService.requestSync();
+
+ // Result
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
}
- sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
- } catch (Exception e) {
- sendErrorToHandler(e);
- }
+ break;
+ case ACTION_SIGN_ENCRYPT:
+
+ try {
+ /* Input */
+ int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
+ Bundle resultData = new Bundle();
+
+ long sigMasterKeyId = data.getLong(ENCRYPT_SIGNATURE_MASTER_ID);
+ String sigKeyPassphrase = data.getString(ENCRYPT_SIGNATURE_KEY_PASSPHRASE);
+
+ byte[] nfcHash = data.getByteArray(ENCRYPT_SIGNATURE_NFC_HASH);
+ Date nfcTimestamp = (Date) data.getSerializable(ENCRYPT_SIGNATURE_NFC_TIMESTAMP);
+
+ String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
+
+ boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
+ long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
+ int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
+ int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1;
+ for (int i = 0; i < urisCount; i++) {
+ data.putInt(SELECTED_URI, i);
+ InputData inputData = createEncryptInputData(data);
+ OutputStream outStream = createCryptOutputStream(data);
+ String originalFilename = getOriginalFilename(data);
+
+ /* Operation */
+ PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
+ this, new ProviderHelper(this), this, inputData, outStream
+ );
+ builder.setEnableAsciiArmorOutput(useAsciiArmor)
+ .setVersionHeader(PgpHelper.getVersionForHeader(this))
+ .setCompressionId(compressionId)
+ .setSymmetricEncryptionAlgorithm(
+ Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
+ .setEncryptionMasterKeyIds(encryptionKeyIds)
+ .setSymmetricPassphrase(symmetricPassphrase)
+ .setOriginalFilename(originalFilename);
+
+ try {
+
+ // Find the appropriate subkey to sign with
+ CachedPublicKeyRing signingRing =
+ new ProviderHelper(this).getCachedPublicKeyRing(sigMasterKeyId);
+ long sigSubKeyId = signingRing.getSecretSignId();
+
+ // Set signature settings
+ builder.setSignatureMasterKeyId(sigMasterKeyId)
+ .setSignatureSubKeyId(sigSubKeyId)
+ .setSignaturePassphrase(sigKeyPassphrase)
+ .setSignatureHashAlgorithm(
+ Preferences.getPreferences(this).getDefaultHashAlgorithm())
+ .setAdditionalEncryptId(sigMasterKeyId);
+ if (nfcHash != null && nfcTimestamp != null) {
+ builder.setNfcState(nfcHash, nfcTimestamp);
+ }
+
+ } catch (PgpKeyNotFoundException e) {
+ // encrypt-only
+ // TODO Just silently drop the requested signature? Shouldn't we throw here?
+ }
+
+ SignEncryptResult result = builder.build().execute();
+ resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result);
+
+ outStream.close();
+
+ /* Output */
+
+ finalizeEncryptOutputStream(data, resultData, outStream);
+
+ }
+
+ Log.logDebugBundle(resultData, "resultData");
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+
+ break;
+ case ACTION_UPLOAD_KEYRING:
+
+ try {
+
+ /* Input */
+ String keyServer = data.getString(UPLOAD_KEY_SERVER);
+ // and dataUri!
+
+ /* Operation */
+ HkpKeyserver server = new HkpKeyserver(keyServer);
+
+ CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
+ ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
+
+ try {
+ importExportOperation.uploadKeyRingToServer(server, keyring);
+ } catch (Keyserver.AddKeyException e) {
+ throw new PgpGeneralException("Unable to export key to selected server");
+ }
+
+ sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
+ } catch (Exception e) {
+ sendErrorToHandler(e);
+ }
+ break;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index 869d2e71b..142814d99 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -39,7 +39,6 @@ import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
@@ -103,7 +102,7 @@ public class PassphraseCacheService extends Service {
private BroadcastReceiver mIntentReceiver;
- private LongSparseArray mPassphraseCache = new LongSparseArray();
+ private LongSparseArray mPassphraseCache = new LongSparseArray<>();
Context mContext;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
index 810190fee..f5df5858c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
import android.os.Parcelable;
+import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import java.io.Serializable;
import java.util.ArrayList;
@@ -49,6 +50,7 @@ public class SaveKeyringParcel implements Parcelable {
public ChangeUnlockParcel mNewUnlock;
public ArrayList mAddUserIds;
+ public ArrayList mAddUserAttribute;
public ArrayList mAddSubKeys;
public ArrayList mChangeSubKeys;
@@ -56,7 +58,6 @@ public class SaveKeyringParcel implements Parcelable {
public ArrayList mRevokeUserIds;
public ArrayList mRevokeSubKeys;
- public ArrayList mStripSubKeys;
public SaveKeyringParcel() {
reset();
@@ -70,13 +71,30 @@ public class SaveKeyringParcel implements Parcelable {
public void reset() {
mNewUnlock = null;
- mAddUserIds = new ArrayList();
- mAddSubKeys = new ArrayList();
+ mAddUserIds = new ArrayList<>();
+ mAddUserAttribute = new ArrayList<>();
+ mAddSubKeys = new ArrayList<>();
mChangePrimaryUserId = null;
- mChangeSubKeys = new ArrayList();
- mRevokeUserIds = new ArrayList();
- mRevokeSubKeys = new ArrayList();
- mStripSubKeys = new ArrayList();
+ mChangeSubKeys = new ArrayList<>();
+ mRevokeUserIds = new ArrayList<>();
+ mRevokeSubKeys = new ArrayList<>();
+ }
+
+ /** Returns true iff this parcel does not contain any operations which require a passphrase. */
+ public boolean isRestrictedOnly() {
+ if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty()
+ || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty()
+ || !mRevokeSubKeys.isEmpty()) {
+ return false;
+ }
+
+ for (SubkeyChange change : mChangeSubKeys) {
+ if (change.mRecertify || change.mFlags != null || change.mExpiry != null) {
+ return false;
+ }
+ }
+
+ return true;
}
// performance gain for using Parcelable here would probably be negligible,
@@ -109,26 +127,53 @@ public class SaveKeyringParcel implements Parcelable {
}
public static class SubkeyChange implements Serializable {
- public long mKeyId;
+ public final long mKeyId;
public Integer mFlags;
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
public Long mExpiry;
+ // if this flag is true, the key will be recertified even if all above
+ // values are no-ops
+ public boolean mRecertify;
+ // if this flag is true, the subkey should be changed to a stripped key
+ public boolean mDummyStrip;
+ // if this is non-null, the subkey will be changed to a divert-to-card
+ // key for the given serial number
+ public byte[] mDummyDivert;
public SubkeyChange(long keyId) {
mKeyId = keyId;
}
+ public SubkeyChange(long keyId, boolean recertify) {
+ mKeyId = keyId;
+ mRecertify = recertify;
+ }
+
public SubkeyChange(long keyId, Integer flags, Long expiry) {
mKeyId = keyId;
mFlags = flags;
mExpiry = expiry;
}
+ public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) {
+ this(keyId, null, null);
+
+ // these flags are mutually exclusive!
+ if (dummyStrip && dummyDivert != null) {
+ throw new AssertionError(
+ "cannot set strip and divert flags at the same time - this is a bug!");
+ }
+ mDummyStrip = dummyStrip;
+ mDummyDivert = dummyDivert;
+ }
+
@Override
public String toString() {
String out = "mKeyId: " + mKeyId + ", ";
out += "mFlags: " + mFlags + ", ";
- out += "mExpiry: " + mExpiry;
+ out += "mExpiry: " + mExpiry + ", ";
+ out += "mDummyStrip: " + mDummyStrip + ", ";
+ out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";
return out;
}
@@ -162,6 +207,7 @@ public class SaveKeyringParcel implements Parcelable {
mNewUnlock = source.readParcelable(getClass().getClassLoader());
mAddUserIds = source.createStringArrayList();
+ mAddUserAttribute = (ArrayList) source.readSerializable();
mAddSubKeys = (ArrayList) source.readSerializable();
mChangeSubKeys = (ArrayList) source.readSerializable();
@@ -169,7 +215,6 @@ public class SaveKeyringParcel implements Parcelable {
mRevokeUserIds = source.createStringArrayList();
mRevokeSubKeys = (ArrayList) source.readSerializable();
- mStripSubKeys = (ArrayList) source.readSerializable();
}
@Override
@@ -184,6 +229,7 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeParcelable(mNewUnlock, 0);
destination.writeStringList(mAddUserIds);
+ destination.writeSerializable(mAddUserAttribute);
destination.writeSerializable(mAddSubKeys);
destination.writeSerializable(mChangeSubKeys);
@@ -191,7 +237,6 @@ public class SaveKeyringParcel implements Parcelable {
destination.writeStringList(mRevokeUserIds);
destination.writeSerializable(mRevokeSubKeys);
- destination.writeSerializable(mStripSubKeys);
}
public static final Creator CREATOR = new Creator() {
@@ -214,12 +259,12 @@ public class SaveKeyringParcel implements Parcelable {
String out = "mMasterKeyId: " + mMasterKeyId + "\n";
out += "mNewUnlock: " + mNewUnlock + "\n";
out += "mAddUserIds: " + mAddUserIds + "\n";
+ out += "mAddUserAttribute: " + mAddUserAttribute + "\n";
out += "mAddSubKeys: " + mAddSubKeys + "\n";
out += "mChangeSubKeys: " + mChangeSubKeys + "\n";
out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n";
out += "mRevokeUserIds: " + mRevokeUserIds + "\n";
- out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n";
- out += "mStripSubKeys: " + mStripSubKeys;
+ out += "mRevokeSubKeys: " + mRevokeSubKeys;
return out;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java
new file mode 100644
index 000000000..7423e6828
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * 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 .
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+
+/**
+ * Setups Toolbar
+ */
+public abstract class BaseActivity extends ActionBarActivity {
+ protected Toolbar mToolbar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initLayout();
+ initToolbar();
+ }
+
+ protected abstract void initLayout();
+
+ protected void initToolbar() {
+ mToolbar = (Toolbar) findViewById(R.id.toolbar);
+ if (mToolbar != null) {
+ setSupportActionBar(mToolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ protected void setActionBarIcon(int iconRes) {
+ mToolbar.setNavigationIcon(iconRes);
+ }
+
+ /**
+ * Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines
+ * see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
+ */
+ protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
+ View.OnClickListener cancelOnClickListener) {
+ setActionBarIcon(R.drawable.ic_close_white_24dp);
+
+ // Inflate the custom action bar view
+ final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext()
+ .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater.inflate(R.layout.full_screen_dialog, null);
+
+ TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.full_screen_dialog_done_text));
+ firstTextView.setText(doneText);
+ customActionBarView.findViewById(R.id.full_screen_dialog_done).setOnClickListener(
+ doneOnClickListener);
+
+ getSupportActionBar().setDisplayShowCustomEnabled(true);
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
+ getSupportActionBar().setCustomView(customActionBarView, new ActionBar.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT,
+ Gravity.END));
+ mToolbar.setNavigationOnClickListener(cancelOnClickListener);
+ }
+
+ /**
+ * Close button only
+ */
+ protected void setFullScreenDialogClose(View.OnClickListener cancelOnClickListener) {
+ setActionBarIcon(R.drawable.ic_close_white_24dp);
+ getSupportActionBar().setDisplayShowTitleEnabled(true);
+ mToolbar.setNavigationOnClickListener(cancelOnClickListener);
+ }
+
+ /**
+ * Inflate custom design with two buttons using drawables.
+ * This does not conform to the Material Design Guidelines, but we deviate here as this is used
+ * to indicate "Allow access"/"Disallow access" to the API, which must be clearly indicated
+ */
+ protected void setFullScreenDialogTwoButtons(int firstText, int firstDrawableId, View.OnClickListener firstOnClickListener,
+ int secondText, int secondDrawableId, View.OnClickListener secondOnClickListener) {
+
+ // Inflate the custom action bar view
+ final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext()
+ .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater.inflate(
+ R.layout.full_screen_dialog_2, null);
+
+ TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
+ firstTextView.setText(firstText);
+ firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ firstOnClickListener);
+ TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
+ secondTextView.setText(secondText);
+ secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
+ customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
+ secondOnClickListener);
+
+ // Show the custom action bar view and hide the normal Home icon and title.
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ getSupportActionBar().setDisplayShowHomeEnabled(false);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ getSupportActionBar().setDisplayShowCustomEnabled(true);
+ getSupportActionBar().setCustomView(customActionBarView, new ActionBar.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
index a97e73934..1fb88b182 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
@@ -18,24 +18,19 @@
package org.sufficientlysecure.keychain.ui;
-import android.os.Bundle;
-import android.support.v7.app.ActionBarActivity;
-
import org.sufficientlysecure.keychain.R;
/**
* Signs the specified public key with the specified secret master key
*/
-public class CertifyKeyActivity extends ActionBarActivity {
+public class CertifyKeyActivity extends BaseActivity {
public static final String EXTRA_RESULT = "operation_result";
public static final String EXTRA_KEY_IDS = "extra_key_ids";
public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id";
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
+ protected void initLayout() {
setContentView(R.layout.certify_key_activity);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
index 4d10d8639..50d5e3229 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
@@ -49,7 +49,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
-import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
+import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
@@ -82,11 +82,11 @@ public class CertifyKeyFragment extends LoaderFragment
private long mSignMasterKeyId = Constants.key.none;
public static final String[] USER_IDS_PROJECTION = new String[]{
- UserIds._ID,
- UserIds.MASTER_KEY_ID,
- UserIds.USER_ID,
- UserIds.IS_PRIMARY,
- UserIds.IS_REVOKED
+ UserPackets._ID,
+ UserPackets.MASTER_KEY_ID,
+ UserPackets.USER_ID,
+ UserPackets.IS_PRIMARY,
+ UserPackets.IS_REVOKED
};
private static final int INDEX_MASTER_KEY_ID = 1;
private static final int INDEX_USER_ID = 2;
@@ -182,7 +182,7 @@ public class CertifyKeyFragment extends LoaderFragment
@Override
public Loader onCreateLoader(int id, Bundle args) {
- Uri uri = UserIds.buildUserIdsUri();
+ Uri uri = UserPackets.buildUserIdsUri();
String selection, ids[];
{
@@ -196,15 +196,15 @@ public class CertifyKeyFragment extends LoaderFragment
}
}
// put together selection string
- selection = UserIds.IS_REVOKED + " = 0" + " AND "
- + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID
+ selection = UserPackets.IS_REVOKED + " = 0" + " AND "
+ + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
+ " IN (" + placeholders + ")";
}
return new CursorLoader(getActivity(), uri,
USER_IDS_PROJECTION, selection, ids,
- Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " ASC"
- + ", " + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC"
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC"
+ + ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"
);
}
@@ -234,7 +234,7 @@ public class CertifyKeyFragment extends LoaderFragment
long lastMasterKeyId = 0;
String lastName = "";
- ArrayList uids = new ArrayList();
+ ArrayList uids = new ArrayList<>();
boolean header = true;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
index 534ac5811..62c38d136 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -20,11 +20,10 @@ package org.sufficientlysecure.keychain.ui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
-import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.R;
-public class CreateKeyActivity extends ActionBarActivity {
+public class CreateKeyActivity extends BaseActivity {
public static final String EXTRA_NAME = "name";
public static final String EXTRA_EMAIL = "email";
@@ -37,8 +36,6 @@ public class CreateKeyActivity extends ActionBarActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.create_key_activity);
-
// pass extras into fragment
CreateKeyInputFragment frag =
CreateKeyInputFragment.newInstance(
@@ -48,6 +45,11 @@ public class CreateKeyActivity extends ActionBarActivity {
loadFragment(null, frag, FRAG_ACTION_START);
}
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.create_key_activity);
+ }
+
public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
index 2804a5ce6..377a9d1f4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
-import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.Log;
public class CreateKeyFinalFragment extends Fragment {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java
index 6079062a7..8aa9fa6db 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyInputFragment.java
@@ -89,7 +89,7 @@ public class CreateKeyInputFragment extends Fragment {
mEmailEdit.setThreshold(1); // Start working from first character
mEmailEdit.setAdapter(
- new ArrayAdapter
+ new ArrayAdapter<>
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
ContactHelper.getPossibleUserEmails(getActivity())
)
@@ -124,7 +124,7 @@ public class CreateKeyInputFragment extends Fragment {
mNameEdit.setThreshold(1); // Start working from first character
mNameEdit.setAdapter(
- new ArrayAdapter