merge master

This commit is contained in:
Ashley Hughes 2014-03-29 13:21:39 +00:00
commit e1d07214df
353 changed files with 26530 additions and 5758 deletions

5
.gitignore vendored
View File

@ -31,3 +31,8 @@ pom.xml.*
#OS Specific #OS Specific
[Tt]humbs.db [Tt]humbs.db
#Lint output
OpenPGP-Keychain/lint-report.html
OpenPGP-Keychain/lint-report_files/*

View File

@ -3,7 +3,7 @@ jdk: oraclejdk7
before_install: before_install:
# Install base Android SDK # Install base Android SDK
- sudo apt-get update -qq - sudo apt-get update -qq
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs; fi - if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi
- wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz - wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz
- tar xzf android-sdk_r22.3-linux.tgz - tar xzf android-sdk_r22.3-linux.tgz
- export ANDROID_HOME=$PWD/android-sdk-linux - export ANDROID_HOME=$PWD/android-sdk-linux

View File

@ -1,3 +1,19 @@
2.4
Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.
* new unified key list
* colorized key fingerprint
* support for keyserver ports
* deactivate possibility to generate weak keys
* much more internal work on the API
* certify user ids
* keyserver query based on machine-readable output
* lock navigation drawer on tablets
* suggestions for emails on creation of keys
* search in public key lists
* and much more improvements and fixes…
2.3.1 2.3.1
* hotfix for crash when upgrading from old versions * hotfix for crash when upgrading from old versions

View File

@ -13,7 +13,8 @@ apply plugin: 'android'
dependencies { dependencies {
compile 'com.android.support:support-v4:19.0.1' compile 'com.android.support:support-v4:19.0.1'
compile project(':libraries:keychain-api-library') compile project(':libraries:openpgp-api-library')
compile project(':libraries:openkeychain-api-library')
} }
android { android {

View File

@ -48,6 +48,7 @@ public class OpenPgpProviderActivity extends Activity {
private Button mEncrypt; private Button mEncrypt;
private Button mSignAndEncrypt; private Button mSignAndEncrypt;
private Button mDecryptAndVerify; private Button mDecryptAndVerify;
private EditText mAccount;
private OpenPgpServiceConnection mServiceConnection; private OpenPgpServiceConnection mServiceConnection;
@ -68,6 +69,7 @@ public class OpenPgpProviderActivity extends Activity {
mEncrypt = (Button) findViewById(R.id.crypto_provider_demo_encrypt); mEncrypt = (Button) findViewById(R.id.crypto_provider_demo_encrypt);
mSignAndEncrypt = (Button) findViewById(R.id.crypto_provider_demo_sign_and_encrypt); mSignAndEncrypt = (Button) findViewById(R.id.crypto_provider_demo_sign_and_encrypt);
mDecryptAndVerify = (Button) findViewById(R.id.crypto_provider_demo_decrypt_and_verify); mDecryptAndVerify = (Button) findViewById(R.id.crypto_provider_demo_decrypt_and_verify);
mAccount = (EditText) findViewById(R.id.crypto_provider_demo_account);
mSign.setOnClickListener(new View.OnClickListener() { mSign.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -142,7 +144,7 @@ public class OpenPgpProviderActivity extends Activity {
private InputStream getInputstream(boolean ciphertext) { private InputStream getInputstream(boolean ciphertext) {
InputStream is = null; InputStream is = null;
try { try {
String inputStr = null; String inputStr;
if (ciphertext) { if (ciphertext) {
inputStr = mCiphertext.getText().toString(); inputStr = mCiphertext.getText().toString();
} else { } else {
@ -213,6 +215,7 @@ public class OpenPgpProviderActivity extends Activity {
public void sign(Intent data) { public void sign(Intent data) {
data.setAction(OpenPgpApi.ACTION_SIGN); data.setAction(OpenPgpApi.ACTION_SIGN);
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
InputStream is = getInputstream(false); InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream(); final ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -225,6 +228,7 @@ public class OpenPgpProviderActivity extends Activity {
data.setAction(OpenPgpApi.ACTION_ENCRYPT); data.setAction(OpenPgpApi.ACTION_ENCRYPT);
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(",")); data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
InputStream is = getInputstream(false); InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream(); final ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -237,6 +241,7 @@ public class OpenPgpProviderActivity extends Activity {
data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT); data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(",")); data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
InputStream is = getInputstream(false); InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream(); final ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -248,6 +253,7 @@ public class OpenPgpProviderActivity extends Activity {
public void decryptAndVerify(Intent data) { public void decryptAndVerify(Intent data) {
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
InputStream is = getInputstream(true); InputStream is = getInputstream(true);
final ByteArrayOutputStream os = new ByteArrayOutputStream(); final ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -264,13 +270,11 @@ public class OpenPgpProviderActivity extends Activity {
// try again after user interaction // try again after user interaction
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
/* /*
* The data originally given to the pgp method are are again * The data originally given to one of the methods above, is again
* returned here to be used when calling again after user interaction. * returned here to be used when calling the method again after user
* * interaction. The Intent now also contains results from the user
* They also contain results from the user interaction which happened, * interaction, for example selected key ids.
* for example selected key ids.
*/ */
switch (requestCode) { switch (requestCode) {
case REQUEST_CODE_SIGN: { case REQUEST_CODE_SIGN: {
sign(data); sign(data);

View File

@ -46,6 +46,7 @@
android:scrollHorizontally="true" android:scrollHorizontally="true"
android:scrollbars="vertical" android:scrollbars="vertical"
android:text="message" android:text="message"
android:hint="cleartext message"
android:textAppearance="@android:style/TextAppearance.Small" /> android:textAppearance="@android:style/TextAppearance.Small" />
</ScrollView> </ScrollView>
@ -66,6 +67,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="ciphertext" android:text="ciphertext"
android:hint="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" /> android:textAppearance="@android:style/TextAppearance.Small" />
</ScrollView> </ScrollView>
@ -104,5 +106,18 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Decrypt and Verify" /> android:text="Decrypt and Verify" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Account ID:"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/textView" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Alice &lt;alice@example.com&gt;"
android:id="@+id/crypto_provider_demo_account" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -0,0 +1,29 @@
#Android specific
bin
gen
obj
lint.xml
local.properties
release.properties
ant.properties
*.class
*.apk
#Gradle
.gradle
build
gradle.properties
#Maven
target
pom.xml.*
#Eclipse
.project
.classpath
.settings
.metadata
#IntelliJ IDEA
.idea
*.iml

View File

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

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:0.8.3' classpath 'com.android.tools.build:gradle:0.9.0'
} }
} }

View File

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

View File

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

View File

@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
android.library=true

View File

@ -126,6 +126,8 @@ public class OpenPgpApi {
/* Intent extras */ /* Intent extras */
public static final String EXTRA_API_VERSION = "api_version"; public static final String EXTRA_API_VERSION = "api_version";
public static final String EXTRA_ACCOUNT_NAME = "account_name";
// SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY // SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
// request ASCII Armor for output // request ASCII Armor for output
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53) // OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)

View File

@ -31,7 +31,7 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.api.R; import org.openintents.openpgp.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,2 +1,3 @@
include ':example-app' include ':example-app'
include ':libraries:keychain-api-library' include ':libraries:openpgp-api-library'
include ':libraries:openkeychain-api-library'

View File

@ -3,7 +3,8 @@ apply plugin: 'android'
dependencies { dependencies {
compile 'com.android.support:support-v4:19.0.1' compile 'com.android.support:support-v4:19.0.1'
compile 'com.android.support:appcompat-v7:19.0.1' compile 'com.android.support:appcompat-v7:19.0.1'
compile project(':OpenPGP-Keychain-API:libraries:keychain-api-library') compile project(':OpenPGP-Keychain-API:libraries:openpgp-api-library')
compile project(':OpenPGP-Keychain-API:libraries:openkeychain-api-library')
compile project(':libraries:HtmlTextView') compile project(':libraries:HtmlTextView')
compile project(':libraries:StickyListHeaders:library') compile project(':libraries:StickyListHeaders:library')
compile project(':libraries:AndroidBootstrap') compile project(':libraries:AndroidBootstrap')
@ -56,5 +57,7 @@ android {
// Do not abort build if lint finds errors // Do not abort build if lint finds errors
lintOptions { lintOptions {
abortOnError false abortOnError false
htmlReport true
htmlOutput file("lint-report.html")
} }
} }

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain" package="org.sufficientlysecure.keychain"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="23103" android:versionCode="24000"
android:versionName="2.3.1 beta3"> android:versionName="2.4">
<!-- <!--
General remarks General remarks
@ -50,6 +50,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! --> <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
<application <application
@ -60,7 +61,7 @@
android:theme="@style/KeychainTheme" android:theme="@style/KeychainTheme"
android:label="@string/app_name"> android:label="@string/app_name">
<activity <activity
android:name=".ui.KeyListPublicActivity" android:name=".ui.KeyListActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop"> android:launchMode="singleTop">
@ -70,12 +71,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.KeyListSecretActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_manage_secret_keys"
android:launchMode="singleTop">
</activity>
<activity <activity
android:name=".ui.EditKeyActivity" android:name=".ui.EditKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -85,32 +80,30 @@
android:name=".ui.ViewKeyActivity" android:name=".ui.ViewKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_key_details" android:label="@string/title_key_details"
android:parentActivityName=".ui.KeyListPublicActivity"> android:parentActivityName=".ui.KeyListActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.KeyListPublicActivity" /> android:value=".ui.KeyListActivity" />
</activity> </activity>
<activity <activity
android:name=".ui.ViewKeyActivityJB" android:name=".ui.ViewKeyActivityJB"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_key_details" android:label="@string/title_key_details"
android:parentActivityName=".ui.KeyListPublicActivity"> android:parentActivityName=".ui.KeyListActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.KeyListPublicActivity" /> android:value=".ui.KeyListActivity" />
</activity> </activity>
<activity <activity
android:name=".ui.SelectPublicKeyActivity" android:name=".ui.SelectPublicKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_recipients" android:label="@string/title_select_recipients"
android:launchMode="singleTop"> android:launchMode="singleTop"></activity>
</activity>
<activity <activity
android:name=".ui.SelectSecretKeyActivity" android:name=".ui.SelectSecretKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_secret_key" android:label="@string/title_select_secret_key"
android:launchMode="singleTop"> android:launchMode="singleTop"></activity>
</activity>
<activity <activity
android:name=".ui.EncryptActivity" android:name=".ui.EncryptActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -149,23 +142,23 @@
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;--> <!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<intent-filter android:label="@string/intent_import_key">--> <!--<intent-filter android:label="@string/intent_import_key">-->
<!--<action android:name="android.intent.action.VIEW" />--> <!--<action android:name="android.intent.action.VIEW" />-->
<!--<category android:name="android.intent.category.BROWSABLE" />--> <!--<category android:name="android.intent.category.BROWSABLE" />-->
<!--<category android:name="android.intent.category.DEFAULT" />--> <!--<category android:name="android.intent.category.DEFAULT" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;--> <!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-signature" />--> <!--<data android:mimeType="application/pgp-signature" />-->
<!--</intent-filter>--> <!--</intent-filter>-->
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;--> <!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<intent-filter android:label="@string/intent_import_key">--> <!--<intent-filter android:label="@string/intent_import_key">-->
<!--<action android:name="android.intent.action.VIEW" />--> <!--<action android:name="android.intent.action.VIEW" />-->
<!--<category android:name="android.intent.category.BROWSABLE" />--> <!--<category android:name="android.intent.category.BROWSABLE" />-->
<!--<category android:name="android.intent.category.DEFAULT" />--> <!--<category android:name="android.intent.category.DEFAULT" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;--> <!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-encrypted" />--> <!--<data android:mimeType="application/pgp-encrypted" />-->
<!--</intent-filter>--> <!--</intent-filter>-->
<!-- Keychain's own Actions --> <!-- Keychain's own Actions -->
<!-- DECRYPT with text as extra --> <!-- DECRYPT with text as extra -->
@ -241,7 +234,7 @@
<activity <activity
android:name=".ui.PreferencesActivity" android:name=".ui.PreferencesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_preferences" > android:label="@string/title_preferences">
<intent-filter> <intent-filter>
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" /> <action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -379,44 +372,49 @@
android:exported="false" android:exported="false"
android:process=":passphrase_cache" /> android:process=":passphrase_cache" />
<service <service
android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" android:name=".service.KeychainIntentService"
android:exported="false" /> android:exported="false" />
<provider <provider
android:name="org.sufficientlysecure.keychain.provider.KeychainProvider" android:name=".provider.KeychainProvider"
android:authorities="org.sufficientlysecure.keychain.provider" android:authorities="org.sufficientlysecure.keychain.provider"
android:exported="false" /> android:exported="false" />
<!-- Internal classes of the remote APIs (not exported) --> <!-- Internal classes of the remote APIs (not exported) -->
<activity <activity
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity" android:name=".remote.ui.RemoteServiceActivity"
android:exported="false" android:exported="false"
android:label="@string/app_name" /> android:label="@string/app_name"
<!--android:launchMode="singleTop"--> android:launchMode="singleTop"
<!--android:process=":remote_api"--> android:process=":remote_api" />
<activity <activity
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity" android:name=".remote.ui.AppsListActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:exported="false" android:exported="false"
android:label="@string/title_api_registered_apps" /> android:label="@string/title_api_registered_apps" />
<activity <activity
android:name="org.sufficientlysecure.keychain.service.remote.AppSettingsActivity" android:name=".remote.ui.AppSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".remote.ui.AppsListActivity" />
</activity>
<activity
android:name=".remote.ui.AccountSettingsActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:exported="false" /> android:exported="false" />
<!-- OpenPGP Remote API --> <!-- OpenPGP Remote API -->
<service <service
android:name="org.sufficientlysecure.keychain.service.remote.OpenPgpService" android:name=".remote.OpenPgpService"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:process=":remote_api"> android:process=":remote_api">
<intent-filter> <intent-filter>
<action android:name="org.openintents.openpgp.IOpenPgpService" /> <action android:name="org.openintents.openpgp.IOpenPgpService" />
</intent-filter> </intent-filter>
<meta-data
android:name="api_version"
android:value="1" />
</service> </service>
<!-- Extended Remote API --> <!-- Extended Remote API -->

View File

@ -16,10 +16,15 @@
package org.sufficientlysecure.keychain; package org.sufficientlysecure.keychain;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import android.os.Environment; import android.os.Environment;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
import org.sufficientlysecure.keychain.ui.DecryptActivity;
import org.sufficientlysecure.keychain.ui.EncryptActivity;
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
import org.sufficientlysecure.keychain.ui.KeyListActivity;
public final class Constants { public final class Constants {
public static final boolean DEBUG = BuildConfig.DEBUG; public static final boolean DEBUG = BuildConfig.DEBUG;
@ -40,12 +45,14 @@ public final class Constants {
public static final String INTENT_PREFIX = PACKAGE_NAME + ".action."; public static final String INTENT_PREFIX = PACKAGE_NAME + ".action.";
public static final class path { public static final class Path {
public static final String APP_DIR = Environment.getExternalStorageDirectory() public static final String APP_DIR = Environment.getExternalStorageDirectory()
+ "/OpenPGP-Keychain"; + "/OpenPGP-Keychain";
public static final String APP_DIR_FILE_SEC = APP_DIR + "/secexport.asc";
public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.asc";
} }
public static final class pref { public static final class Pref {
public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm"; public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm";
public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm"; public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm";
public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour"; public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour";
@ -57,8 +64,17 @@ public final class Constants {
public static final String KEY_SERVERS = "keyServers"; public static final String KEY_SERVERS = "keyServers";
} }
public static final class defaults { public static final class Defaults {
public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu"; public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu";
} }
public static final class DrawerItems {
public static final Class KEY_LIST = KeyListActivity.class;
public static final Class ENCRYPT = EncryptActivity.class;
public static final Class DECRYPT = DecryptActivity.class;
public static final Class IMPORT_KEYS = ImportKeysActivity.class;
public static final Class REGISTERED_APPS_LIST = AppsListActivity.class;
public static final Class[] ARRAY = new Class[]{KEY_LIST, ENCRYPT, DECRYPT,
IMPORT_KEYS, REGISTERED_APPS_LIST};
}
} }

View File

@ -119,6 +119,7 @@ public final class Id {
public static final int secret_key = 0x21070002; public static final int secret_key = 0x21070002;
public static final int user_id = 0x21070003; public static final int user_id = 0x21070003;
public static final int key = 0x21070004; public static final int key = 0x21070004;
public static final int public_secret_key = 0x21070005;
} }
public static final class choice { public static final class choice {

View File

@ -64,7 +64,7 @@ public class KeychainApplication extends Application {
// Create APG directory on sdcard if not existing // Create APG directory on sdcard if not existing
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dir = new File(Constants.path.APP_DIR); File dir = new File(Constants.Path.APP_DIR);
if (!dir.exists() && !dir.mkdirs()) { if (!dir.exists() && !dir.mkdirs()) {
// ignore this for now, it's not crucial // ignore this for now, it's not crucial
// that the directory doesn't exist at this point // that the directory doesn't exist at this point

View File

@ -59,7 +59,6 @@ public class ClipboardReflection {
* Wrapper around ClipboardManager based on Android version using Reflection API * Wrapper around ClipboardManager based on Android version using Reflection API
* *
* @param context * @param context
* @param text
*/ */
public static CharSequence getClipboardText(Context context) { public static CharSequence getClipboardText(Context context) {
Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);

View File

@ -37,8 +37,9 @@ import android.os.Handler;
* </code> * </code>
*/ */
public class DialogFragmentWorkaround { public class DialogFragmentWorkaround {
public static final SDKLevel17Interface INTERFACE = ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ? new SDKLevel17Impl() public static final SDKLevel17Interface INTERFACE =
: new SDKLevelPriorLevel17Impl()); ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ? new SDKLevel17Impl()
: new SDKLevelPriorLevel17Impl());
private static final int RUNNABLE_DELAY = 300; private static final int RUNNABLE_DELAY = 300;

View File

@ -17,18 +17,17 @@
package org.sufficientlysecure.keychain.helper; package org.sufficientlysecure.keychain.helper;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity; import android.app.Activity;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class ActionBarHelper { public class ActionBarHelper {
@ -38,7 +37,6 @@ public class ActionBarHelper {
* @param activity * @param activity
*/ */
public static void setBackButton(ActionBarActivity activity) { public static void setBackButton(ActionBarActivity activity) {
// set actionbar without home button if called from another app
final ActionBar actionBar = activity.getSupportActionBar(); final ActionBar actionBar = activity.getSupportActionBar();
Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)=" Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
+ activity.getCallingPackage()); + activity.getCallingPackage());
@ -56,28 +54,33 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done/Cancel activities * Sets custom view on ActionBar for Done/Cancel activities
* *
* @param actionBar * @param actionBar
* @param doneText * @param firstText
* @param doneOnClickListener * @param firstDrawableId
* @param cancelText * @param firstOnClickListener
* @param cancelOnClickListener * @param secondText
* @param secondDrawableId
* @param secondOnClickListener
*/ */
public static void setDoneCancelView(ActionBar actionBar, int doneText, public static void setTwoButtonView(ActionBar actionBar,
OnClickListener doneOnClickListener, int cancelText, int firstText, int firstDrawableId, OnClickListener firstOnClickListener,
OnClickListener cancelOnClickListener) { int secondText, int secondDrawableId, OnClickListener secondOnClickListener) {
// Inflate a "Done"/"Cancel" custom action bar view // Inflate the custom action bar view
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater.inflate( final View customActionBarView = inflater.inflate(
R.layout.actionbar_custom_view_done_cancel, null); R.layout.actionbar_custom_view_done_cancel, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); 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( customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
doneOnClickListener); firstOnClickListener);
((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)) TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
.setText(cancelText); secondTextView.setText(secondText);
secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
cancelOnClickListener); secondOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title. // Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -91,20 +94,22 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done activities * Sets custom view on ActionBar for Done activities
* *
* @param actionBar * @param actionBar
* @param doneText * @param firstText
* @param doneOnClickListener * @param firstOnClickListener
*/ */
public static void setDoneView(ActionBar actionBar, int doneText, public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
OnClickListener doneOnClickListener) { OnClickListener firstOnClickListener) {
// Inflate a "Done" custom action bar view to serve as the "Up" affordance. // Inflate a "Done" custom action bar view to serve as the "Up" affordance.
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater final View customActionBarView = inflater
.inflate(R.layout.actionbar_custom_view_done, null); .inflate(R.layout.actionbar_custom_view_done, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); 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( customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
doneOnClickListener); firstOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title. // Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -112,5 +117,4 @@ public class ActionBarHelper {
actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(customActionBarView); actionBar.setCustomView(customActionBarView);
} }
} }

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.helper;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.util.Patterns;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ContactHelper {
public static final List<String> getMailAccounts(Context context) {
final Account[] accounts = AccountManager.get(context).getAccounts();
final Set<String> emailSet = new HashSet<String>();
for (Account account : accounts) {
if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
emailSet.add(account.name);
}
}
return new ArrayList<String>(emailSet);
}
}

View File

@ -16,18 +16,8 @@
package org.sufficientlysecure.keychain.helper; package org.sufficientlysecure.keychain.helper;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -36,35 +26,50 @@ import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.widget.Toast; import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.lang.reflect.Array;
import java.security.Provider;
import java.util.ArrayList;
public class ExportHelper { public class ExportHelper {
protected FileDialogFragment mFileDialog; protected FileDialogFragment mFileDialog;
protected String mExportFilename; protected String mExportFilename;
ActionBarActivity activity; ActionBarActivity mActivity;
public ExportHelper(ActionBarActivity activity) { public ExportHelper(ActionBarActivity activity) {
super(); super();
this.activity = activity; this.mActivity = activity;
} }
public void deleteKey(Uri dataUri, final int keyType, Handler deleteHandler) { public void deleteKey(Uri dataUri, Handler deleteHandler) {
long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment()); long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment());
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
Messenger messenger = new Messenger(deleteHandler); Messenger messenger = new Messenger(deleteHandler);
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
new long[] { keyRingRowId }, keyType); new long[]{keyRingRowId});
deleteKeyDialog.show(activity.getSupportFragmentManager(), "deleteKeyDialog"); deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog");
} }
/** /**
* Show dialog where to export keys * Show dialog where to export keys
*/ */
public void showExportKeysDialog(final Uri dataUri, final int keyType, public void showExportKeysDialog(final long[] masterKeyIds, final int keyType,
final String exportFilename) { final String exportFilename, final String checkboxString) {
mExportFilename = exportFilename; mExportFilename = exportFilename;
// Message is received after file is selected // Message is received after file is selected
@ -73,9 +78,14 @@ public class ExportHelper {
public void handleMessage(Message message) { public void handleMessage(Message message) {
if (message.what == FileDialogFragment.MESSAGE_OKAY) { if (message.what == FileDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData(); Bundle data = message.getData();
int type = keyType;
mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
exportKeys(dataUri, keyType); if( data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED) ) {
type = Id.type.public_secret_key;
}
exportKeys(masterKeyIds, type);
} }
} }
}; };
@ -86,25 +96,20 @@ public class ExportHelper {
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() { public void run() {
String title = null; String title = null;
if (dataUri == null) { if (masterKeyIds == null) {
// export all keys // export all keys
title = activity.getString(R.string.title_export_keys); title = mActivity.getString(R.string.title_export_keys);
} else { } else {
// export only key specified at data uri // export only key specified at data uri
title = activity.getString(R.string.title_export_key); title = mActivity.getString(R.string.title_export_key);
} }
String message = null; String message = mActivity.getString(R.string.specify_file_to_export_to);
if (keyType == Id.type.public_key) {
message = activity.getString(R.string.specify_file_to_export_to);
} else {
message = activity.getString(R.string.specify_file_to_export_secret_keys_to);
}
mFileDialog = FileDialogFragment.newInstance(messenger, title, message, mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
exportFilename, null); exportFilename, checkboxString);
mFileDialog.show(activity.getSupportFragmentManager(), "fileDialog"); mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
} }
}); });
} }
@ -112,11 +117,11 @@ public class ExportHelper {
/** /**
* Export keys * Export keys
*/ */
public void exportKeys(Uri dataUri, int keyType) { public void exportKeys(long[] masterKeyIds, int keyType) {
Log.d(Constants.TAG, "exportKeys started"); Log.d(Constants.TAG, "exportKeys started");
// Send all information needed to service to export key in other thread // Send all information needed to service to export key in other thread
Intent intent = new Intent(activity, KeychainIntentService.class); final Intent intent = new Intent(mActivity, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING); intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING);
@ -126,22 +131,27 @@ public class ExportHelper {
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType);
if (dataUri == null) { if (masterKeyIds == null) {
data.putBoolean(KeychainIntentService.EXPORT_ALL, true); data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
} else { } else {
// TODO: put data uri into service??? data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);
long keyRingMasterKeyId = ProviderHelper.getMasterKeyId(activity, dataUri);
data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId);
} }
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after exporting is done in ApgService // Message is received after exporting is done in KeychainIntentService
KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity, KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { mActivity.getString(R.string.progress_exporting),
ProgressDialog.STYLE_HORIZONTAL,
true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
mActivity.stopService(intent);
}
}) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -151,16 +161,16 @@ public class ExportHelper {
int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT); int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT);
String toastMessage; String toastMessage;
if (exported == 1) { if (exported == 1) {
toastMessage = activity.getString(R.string.key_exported); toastMessage = mActivity.getString(R.string.key_exported);
} else if (exported > 0) { } else if (exported > 0) {
toastMessage = activity.getString(R.string.keys_exported, exported); toastMessage = mActivity.getString(R.string.keys_exported, exported);
} else { } else {
toastMessage = activity.getString(R.string.no_keys_exported); toastMessage = mActivity.getString(R.string.no_keys_exported);
} }
Toast.makeText(activity, toastMessage, Toast.LENGTH_SHORT).show(); Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -168,10 +178,10 @@ public class ExportHelper {
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog // show progress dialog
exportHandler.showProgressDialog(activity); exportHandler.showProgressDialog(mActivity);
// start service with intent // start service with intent
activity.startService(intent); mActivity.startService(intent);
} }
} }

View File

@ -17,10 +17,6 @@
package org.sufficientlysecure.keychain.helper; package org.sufficientlysecure.keychain.helper;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
@ -30,6 +26,9 @@ import android.net.Uri;
import android.os.Environment; import android.os.Environment;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.widget.Toast; import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class FileHelper { public class FileHelper {
@ -54,13 +53,10 @@ public class FileHelper {
* installed. * installed.
* *
* @param activity * @param activity
* @param filename * @param filename default selected file, not supported by all file managers
* default selected file, not supported by all file managers * @param mimeType can be text/plain for example
* @param mimeType * @param requestCode requestCode used to identify the result coming back from file manager to
* can be text/plain for example * onActivityResult() in your activity
* @param requestCode
* requestCode used to identify the result coming back from file manager to
* onActivityResult() in your activity
*/ */
public static void openFile(Activity activity, String filename, String mimeType, int requestCode) { public static void openFile(Activity activity, String filename, String mimeType, int requestCode) {
Intent intent = buildFileIntent(filename, mimeType); Intent intent = buildFileIntent(filename, mimeType);
@ -97,14 +93,13 @@ public class FileHelper {
/** /**
* Get a file path from a Uri. * Get a file path from a Uri.
* * <p/>
* from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/ * from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/
* afilechooser/utils/FileUtils.java * afilechooser/utils/FileUtils.java
* *
* @param context * @param context
* @param uri * @param uri
* @return * @return
*
* @author paulburke * @author paulburke
*/ */
public static String getPath(Context context, Uri uri) { public static String getPath(Context context, Uri uri) {
@ -115,21 +110,19 @@ public class FileHelper {
+ uri.getPathSegments().toString()); + uri.getPathSegments().toString());
if ("content".equalsIgnoreCase(uri.getScheme())) { if ("content".equalsIgnoreCase(uri.getScheme())) {
String[] projection = { "_data" }; String[] projection = {"_data"};
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = context.getContentResolver().query(uri, projection, null, null, null); cursor = context.getContentResolver().query(uri, projection, null, null, null);
int column_index = cursor.getColumnIndexOrThrow("_data"); int columnIndex = cursor.getColumnIndexOrThrow("_data");
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
return cursor.getString(column_index); return cursor.getString(columnIndex);
} }
} catch (Exception e) { } catch (Exception e) {
// Eat it // Eat it
} }
} } else if ("file".equalsIgnoreCase(uri.getScheme())) {
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath(); return uri.getPath();
} }

View File

@ -17,15 +17,16 @@
package org.sufficientlysecure.keychain.helper; package org.sufficientlysecure.keychain.helper;
import java.util.Calendar; import android.os.Bundle;
import java.util.GregorianCalendar; import android.text.SpannableStringBuilder;
import java.util.Iterator; import android.text.Spanned;
import java.util.Set;
import android.text.style.StrikethroughSpan;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.os.Bundle; import java.util.Iterator;
import java.util.Set;
public class OtherHelper { public class OtherHelper {
@ -60,4 +61,10 @@ public class OtherHelper {
} }
} }
public static SpannableStringBuilder strikeOutText(CharSequence text) {
SpannableStringBuilder sb = new SpannableStringBuilder(text);
sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
return sb;
}
} }

View File

@ -17,14 +17,13 @@
package org.sufficientlysecure.keychain.helper; package org.sufficientlysecure.keychain.helper;
import android.content.Context;
import android.content.SharedPreferences;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Vector; import java.util.Vector;
/** /**
@ -38,8 +37,8 @@ public class Preferences {
return getPreferences(context, false); return getPreferences(context, false);
} }
public static synchronized Preferences getPreferences(Context context, boolean force_new) { public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
if (mPreferences == null || force_new) { if (mPreferences == null || forceNew) {
mPreferences = new Preferences(context); mPreferences = new Preferences(context);
} }
return mPreferences; return mPreferences;
@ -50,17 +49,17 @@ public class Preferences {
} }
public String getLanguage() { public String getLanguage() {
return mSharedPreferences.getString(Constants.pref.LANGUAGE, ""); return mSharedPreferences.getString(Constants.Pref.LANGUAGE, "");
} }
public void setLanguage(String value) { public void setLanguage(String value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Constants.pref.LANGUAGE, value); editor.putString(Constants.Pref.LANGUAGE, value);
editor.commit(); editor.commit();
} }
public long getPassPhraseCacheTtl() { public long getPassPhraseCacheTtl() {
int ttl = mSharedPreferences.getInt(Constants.pref.PASS_PHRASE_CACHE_TTL, 180); int ttl = mSharedPreferences.getInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, 180);
// fix the value if it was set to "never" in previous versions, which currently is not // fix the value if it was set to "never" in previous versions, which currently is not
// supported // supported
if (ttl == 0) { if (ttl == 0) {
@ -71,81 +70,81 @@ public class Preferences {
public void setPassPhraseCacheTtl(int value) { public void setPassPhraseCacheTtl(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.PASS_PHRASE_CACHE_TTL, value); editor.putInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, value);
editor.commit(); editor.commit();
} }
public int getDefaultEncryptionAlgorithm() { public int getDefaultEncryptionAlgorithm() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, return mSharedPreferences.getInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM,
PGPEncryptedData.AES_256); PGPEncryptedData.AES_256);
} }
public void setDefaultEncryptionAlgorithm(int value) { public void setDefaultEncryptionAlgorithm(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, value); editor.putInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, value);
editor.commit(); editor.commit();
} }
public int getDefaultHashAlgorithm() { public int getDefaultHashAlgorithm() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_HASH_ALGORITHM, return mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM,
HashAlgorithmTags.SHA512); HashAlgorithmTags.SHA512);
} }
public void setDefaultHashAlgorithm(int value) { public void setDefaultHashAlgorithm(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_HASH_ALGORITHM, value); editor.putInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, value);
editor.commit(); editor.commit();
} }
public int getDefaultMessageCompression() { public int getDefaultMessageCompression() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
Id.choice.compression.zlib); Id.choice.compression.zlib);
} }
public void setDefaultMessageCompression(int value) { public void setDefaultMessageCompression(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, value); editor.putInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, value);
editor.commit(); editor.commit();
} }
public int getDefaultFileCompression() { public int getDefaultFileCompression() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_FILE_COMPRESSION, return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
Id.choice.compression.none); Id.choice.compression.none);
} }
public void setDefaultFileCompression(int value) { public void setDefaultFileCompression(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_FILE_COMPRESSION, value); editor.putInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, value);
editor.commit(); editor.commit();
} }
public boolean getDefaultAsciiArmour() { public boolean getDefaultAsciiArmour() {
return mSharedPreferences.getBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, false); return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, false);
} }
public void setDefaultAsciiArmour(boolean value) { public void setDefaultAsciiArmour(boolean value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, value); editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, value);
editor.commit(); editor.commit();
} }
public boolean getForceV3Signatures() { public boolean getForceV3Signatures() {
return mSharedPreferences.getBoolean(Constants.pref.FORCE_V3_SIGNATURES, false); return mSharedPreferences.getBoolean(Constants.Pref.FORCE_V3_SIGNATURES, false);
} }
public void setForceV3Signatures(boolean value) { public void setForceV3Signatures(boolean value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.pref.FORCE_V3_SIGNATURES, value); editor.putBoolean(Constants.Pref.FORCE_V3_SIGNATURES, value);
editor.commit(); editor.commit();
} }
public String[] getKeyServers() { public String[] getKeyServers() {
String rawData = mSharedPreferences.getString(Constants.pref.KEY_SERVERS, String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
Constants.defaults.KEY_SERVERS); Constants.Defaults.KEY_SERVERS);
Vector<String> servers = new Vector<String>(); Vector<String> servers = new Vector<String>();
String chunks[] = rawData.split(","); String chunks[] = rawData.split(",");
for (int i = 0; i < chunks.length; ++i) { for (String c : chunks) {
String tmp = chunks[i].trim(); String tmp = c.trim();
if (tmp.length() > 0) { if (tmp.length() > 0) {
servers.add(tmp); servers.add(tmp);
} }
@ -156,8 +155,8 @@ public class Preferences {
public void setKeyServers(String[] value) { public void setKeyServers(String[] value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
String rawData = ""; String rawData = "";
for (int i = 0; i < value.length; ++i) { for (String v : value) {
String tmp = value[i].trim(); String tmp = v.trim();
if (tmp.length() == 0) { if (tmp.length() == 0) {
continue; continue;
} }
@ -166,7 +165,7 @@ public class Preferences {
} }
rawData += tmp; rawData += tmp;
} }
editor.putString(Constants.pref.KEY_SERVERS, rawData); editor.putString(Constants.Pref.KEY_SERVERS, rawData);
editor.commit(); editor.commit();
} }
} }

View File

@ -17,11 +17,6 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
@ -29,6 +24,11 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
public class PgpConversionHelper { public class PgpConversionHelper {
@ -90,10 +90,10 @@ public class PgpConversionHelper {
/** /**
* Convert from byte[] to PGPSecretKey * Convert from byte[] to PGPSecretKey
* * <p/>
* Singles keys are encoded as keyRings with one single key in it by Bouncy Castle * Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
* *
* @param keysBytes * @param keyBytes
* @return * @return
*/ */
public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) { public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
@ -105,13 +105,13 @@ public class PgpConversionHelper {
Log.e(Constants.TAG, "Error while converting to PGPSecretKey!", e); Log.e(Constants.TAG, "Error while converting to PGPSecretKey!", e);
} }
PGPSecretKey secKey = null; PGPSecretKey secKey = null;
if(obj instanceof PGPSecretKey) { if (obj instanceof PGPSecretKey) {
if ((secKey = (PGPSecretKey)obj ) == null) { if ((secKey = (PGPSecretKey) obj) == null) {
Log.e(Constants.TAG, "No keys given!"); Log.e(Constants.TAG, "No keys given!");
} }
} else if(obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings } else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
PGPSecretKeyRing keyRing = null; PGPSecretKeyRing keyRing = null;
if ((keyRing = (PGPSecretKeyRing)obj) == null) { if ((keyRing = (PGPSecretKeyRing) obj) == null) {
Log.e(Constants.TAG, "No keys given!"); Log.e(Constants.TAG, "No keys given!");
} }
secKey = keyRing.getSecretKey(); secKey = keyRing.getSecretKey();
@ -142,7 +142,7 @@ public class PgpConversionHelper {
/** /**
* Convert from PGPSecretKey to byte[] * Convert from PGPSecretKey to byte[]
* *
* @param keysBytes * @param key
* @return * @return
*/ */
public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) { public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
@ -158,7 +158,7 @@ public class PgpConversionHelper {
/** /**
* Convert from PGPSecretKeyRing to byte[] * Convert from PGPSecretKeyRing to byte[]
* *
* @param keysBytes * @param keyRing
* @return * @return
*/ */
public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) { public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {

View File

@ -18,38 +18,16 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import android.content.Context; import android.content.Context;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.bcpg.ArmoredInputStream; import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.openpgp.PGPCompressedData; import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
import org.spongycastle.openpgp.PGPPBEEncryptedData;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.*;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@ -59,12 +37,7 @@ import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import java.io.BufferedInputStream; import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.Iterator; import java.util.Iterator;
@ -72,57 +45,57 @@ import java.util.Iterator;
* This class uses a Builder pattern! * This class uses a Builder pattern!
*/ */
public class PgpDecryptVerify { public class PgpDecryptVerify {
private Context context; private Context mContext;
private InputData data; private InputData mData;
private OutputStream outStream; private OutputStream mOutStream;
private ProgressDialogUpdater progressDialogUpdater; private ProgressDialogUpdater mProgressDialogUpdater;
private boolean assumeSymmetric; private boolean mAssumeSymmetric;
private String passphrase; private String mPassphrase;
private long enforcedKeyId; private long mEnforcedKeyId;
private PgpDecryptVerify(Builder builder) { private PgpDecryptVerify(Builder builder) {
// private Constructor can only be called from Builder // private Constructor can only be called from Builder
this.context = builder.context; this.mContext = builder.mContext;
this.data = builder.data; this.mData = builder.mData;
this.outStream = builder.outStream; this.mOutStream = builder.mOutStream;
this.progressDialogUpdater = builder.progressDialogUpdater; this.mProgressDialogUpdater = builder.mProgressDialogUpdater;
this.assumeSymmetric = builder.assumeSymmetric; this.mAssumeSymmetric = builder.mAssumeSymmetric;
this.passphrase = builder.passphrase; this.mPassphrase = builder.mPassphrase;
this.enforcedKeyId = builder.enforcedKeyId; this.mEnforcedKeyId = builder.mEnforcedKeyId;
} }
public static class Builder { public static class Builder {
// mandatory parameter // mandatory parameter
private Context context; private Context mContext;
private InputData data; private InputData mData;
private OutputStream outStream; private OutputStream mOutStream;
// optional // optional
private ProgressDialogUpdater progressDialogUpdater = null; private ProgressDialogUpdater mProgressDialogUpdater = null;
private boolean assumeSymmetric = false; private boolean mAssumeSymmetric = false;
private String passphrase = ""; private String mPassphrase = "";
private long enforcedKeyId = 0; private long mEnforcedKeyId = 0;
public Builder(Context context, InputData data, OutputStream outStream) { public Builder(Context context, InputData data, OutputStream outStream) {
this.context = context; this.mContext = context;
this.data = data; this.mData = data;
this.outStream = outStream; this.mOutStream = outStream;
} }
public Builder progressDialogUpdater(ProgressDialogUpdater progressDialogUpdater) { public Builder progressDialogUpdater(ProgressDialogUpdater progressDialogUpdater) {
this.progressDialogUpdater = progressDialogUpdater; this.mProgressDialogUpdater = progressDialogUpdater;
return this; return this;
} }
public Builder assumeSymmetric(boolean assumeSymmetric) { public Builder assumeSymmetric(boolean assumeSymmetric) {
this.assumeSymmetric = assumeSymmetric; this.mAssumeSymmetric = assumeSymmetric;
return this; return this;
} }
public Builder passphrase(String passphrase) { public Builder passphrase(String passphrase) {
this.passphrase = passphrase; this.mPassphrase = passphrase;
return this; return this;
} }
@ -134,7 +107,7 @@ public class PgpDecryptVerify {
* @return * @return
*/ */
public Builder enforcedKeyId(long enforcedKeyId) { public Builder enforcedKeyId(long enforcedKeyId) {
this.enforcedKeyId = enforcedKeyId; this.mEnforcedKeyId = enforcedKeyId;
return this; return this;
} }
@ -144,14 +117,14 @@ public class PgpDecryptVerify {
} }
public void updateProgress(int message, int current, int total) { public void updateProgress(int message, int current, int total) {
if (progressDialogUpdater != null) { if (mProgressDialogUpdater != null) {
progressDialogUpdater.setProgress(message, current, total); mProgressDialogUpdater.setProgress(message, current, total);
} }
} }
public void updateProgress(int current, int total) { public void updateProgress(int current, int total) {
if (progressDialogUpdater != null) { if (mProgressDialogUpdater != null) {
progressDialogUpdater.setProgress(current, total); mProgressDialogUpdater.setProgress(current, total);
} }
} }
@ -196,7 +169,7 @@ public class PgpDecryptVerify {
public PgpDecryptVerifyResult execute() public PgpDecryptVerifyResult execute()
throws IOException, PgpGeneralException, PGPException, SignatureException { throws IOException, PgpGeneralException, PGPException, SignatureException {
// automatically works with ascii armor input and binary // automatically works with ascii armor input and binary
InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
if (in instanceof ArmoredInputStream) { if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in; ArmoredInputStream aIn = (ArmoredInputStream) in;
// it is ascii armored // it is ascii armored
@ -240,7 +213,7 @@ public class PgpDecryptVerify {
} }
if (enc == null) { if (enc == null) {
throw new PgpGeneralException(context.getString(R.string.error_invalid_data)); throw new PgpGeneralException(mContext.getString(R.string.error_invalid_data));
} }
InputStream clear; InputStream clear;
@ -250,7 +223,7 @@ public class PgpDecryptVerify {
// TODO: currently we always only look at the first known key or symmetric encryption, // TODO: currently we always only look at the first known key or symmetric encryption,
// there might be more... // there might be more...
if (assumeSymmetric) { if (mAssumeSymmetric) {
PGPPBEEncryptedData pbe = null; PGPPBEEncryptedData pbe = null;
Iterator<?> it = enc.getEncryptedDataObjects(); Iterator<?> it = enc.getEncryptedDataObjects();
// find secret key // find secret key
@ -264,7 +237,7 @@ public class PgpDecryptVerify {
if (pbe == null) { if (pbe == null) {
throw new PgpGeneralException( throw new PgpGeneralException(
context.getString(R.string.error_no_symmetric_encryption_packet)); mContext.getString(R.string.error_no_symmetric_encryption_packet));
} }
updateProgress(R.string.progress_preparing_streams, currentProgress, 100); updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
@ -273,7 +246,7 @@ public class PgpDecryptVerify {
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray()); mPassphrase.toCharArray());
clear = pbe.getDataStream(decryptorFactory); clear = pbe.getDataStream(decryptorFactory);
@ -290,33 +263,37 @@ public class PgpDecryptVerify {
Object obj = it.next(); Object obj = it.next();
if (obj instanceof PGPPublicKeyEncryptedData) { if (obj instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID()); secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID());
if (secretKey != null) { if (secretKey != null) {
// secret key exists in database // secret key exists in database
// allow only a specific key for decryption? // allow only a specific key for decryption?
if (enforcedKeyId != 0) { if (mEnforcedKeyId != 0) {
// TODO: improve this code! get master key directly! // TODO: improve this code! get master key directly!
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, encData.getKeyID()); PGPSecretKeyRing secretKeyRing =
ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, encData.getKeyID());
long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID(); long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID();
Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID()); Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID());
Log.d(Constants.TAG, "enforcedKeyId: " + enforcedKeyId); Log.d(Constants.TAG, "enforcedKeyId: " + mEnforcedKeyId);
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId); Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
if (enforcedKeyId != masterKeyId) { if (mEnforcedKeyId != masterKeyId) {
throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found)); throw new PgpGeneralException(
mContext.getString(R.string.error_no_secret_key_found));
} }
} }
pbe = encData; pbe = encData;
// if no passphrase was explicitly set try to get it from the cache service // if no passphrase was explicitly set try to get it from the cache service
if (passphrase == null) { if (mPassphrase == null) {
// returns "" if key has no passphrase // returns "" if key has no passphrase
passphrase = PassphraseCacheService.getCachedPassphrase(context, encData.getKeyID()); mPassphrase =
PassphraseCacheService.getCachedPassphrase(mContext, encData.getKeyID());
// if passphrase was not cached, return here indicating that a passphrase is missing! // if passphrase was not cached, return here
if (passphrase == null) { // indicating that a passphrase is missing!
if (mPassphrase == null) {
returnData.setKeyPassphraseNeeded(true); returnData.setKeyPassphraseNeeded(true);
return returnData; return returnData;
} }
@ -330,7 +307,7 @@ public class PgpDecryptVerify {
} }
if (secretKey == null) { if (secretKey == null) {
throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found)); throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
} }
currentProgress += 5; currentProgress += 5;
@ -339,14 +316,14 @@ public class PgpDecryptVerify {
try { try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.toCharArray()); mPassphrase.toCharArray());
privateKey = secretKey.extractPrivateKey(keyDecryptor); privateKey = secretKey.extractPrivateKey(keyDecryptor);
} catch (PGPException e) { } catch (PGPException e) {
throw new PGPException(context.getString(R.string.error_wrong_passphrase)); throw new PGPException(mContext.getString(R.string.error_wrong_passphrase));
} }
if (privateKey == null) { if (privateKey == null) {
throw new PgpGeneralException( throw new PgpGeneralException(
context.getString(R.string.error_could_not_extract_private_key)); mContext.getString(R.string.error_could_not_extract_private_key));
} }
currentProgress += 5; currentProgress += 5;
updateProgress(R.string.progress_preparing_streams, currentProgress, 100); updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
@ -386,7 +363,7 @@ public class PgpDecryptVerify {
for (int i = 0; i < sigList.size(); ++i) { for (int i = 0; i < sigList.size(); ++i) {
signature = sigList.get(i); signature = sigList.get(i);
signatureKey = ProviderHelper signatureKey = ProviderHelper
.getPGPPublicKeyByKeyId(context, signature.getKeyID()); .getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
if (signatureKeyId == 0) { if (signatureKeyId == 0) {
signatureKeyId = signature.getKeyID(); signatureKeyId = signature.getKeyID();
} }
@ -397,7 +374,7 @@ public class PgpDecryptVerify {
signatureKeyId = signature.getKeyID(); signatureKeyId = signature.getKeyID();
String userId = null; String userId = null;
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId( PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(
context, signatureKeyId); mContext, signatureKeyId);
if (signKeyRing != null) { if (signKeyRing != null) {
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
} }
@ -409,7 +386,8 @@ public class PgpDecryptVerify {
signatureResult.setKeyId(signatureKeyId); signatureResult.setKeyId(signatureKeyId);
if (signature != null) { if (signature != null) {
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider() JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signature.init(contentVerifierBuilderProvider, signatureKey); signature.init(contentVerifierBuilderProvider, signatureKey);
@ -444,9 +422,9 @@ public class PgpDecryptVerify {
int n; int n;
// TODO: progress calculation is broken here! Try to rework it based on commented code! // TODO: progress calculation is broken here! Try to rework it based on commented code!
// int progress = 0; // int progress = 0;
long startPos = data.getStreamPosition(); long startPos = mData.getStreamPosition();
while ((n = dataIn.read(buffer)) > 0) { while ((n = dataIn.read(buffer)) > 0) {
outStream.write(buffer, 0, n); mOutStream.write(buffer, 0, n);
// progress += n; // progress += n;
if (signature != null) { if (signature != null) {
try { try {
@ -460,11 +438,11 @@ public class PgpDecryptVerify {
// unknown size, but try to at least have a moving, slowing down progress bar // unknown size, but try to at least have a moving, slowing down progress bar
// currentProgress = startProgress + (endProgress - startProgress) * progress // currentProgress = startProgress + (endProgress - startProgress) * progress
// / (progress + 100000); // / (progress + 100000);
if (data.getSize() - startPos == 0) { if (mData.getSize() - startPos == 0) {
currentProgress = endProgress; currentProgress = endProgress;
} else { } else {
currentProgress = (int) (startProgress + (endProgress - startProgress) currentProgress = (int) (startProgress + (endProgress - startProgress)
* (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); * (mData.getStreamPosition() - startPos) / (mData.getSize() - startPos));
} }
updateProgress(currentProgress, 100); updateProgress(currentProgress, 100);
} }
@ -480,7 +458,7 @@ public class PgpDecryptVerify {
signatureResult.setSignatureOnly(false); signatureResult.setSignatureOnly(false);
//Now check binding signatures //Now check binding signatures
boolean validKeyBinding = verifyKeyBinding(context, messageSignature, signatureKey); boolean validKeyBinding = verifyKeyBinding(mContext, messageSignature, signatureKey);
boolean validSignature = signature.verify(messageSignature); boolean validSignature = signature.verify(messageSignature);
// TODO: implement CERTIFIED! // TODO: implement CERTIFIED!
@ -499,7 +477,7 @@ public class PgpDecryptVerify {
} else { } else {
// failed // failed
Log.d(Constants.TAG, "Integrity verification: failed!"); Log.d(Constants.TAG, "Integrity verification: failed!");
throw new PgpGeneralException(context.getString(R.string.error_integrity_check_failed)); throw new PgpGeneralException(mContext.getString(R.string.error_integrity_check_failed));
} }
} else { } else {
// no integrity check // no integrity check
@ -555,21 +533,21 @@ public class PgpDecryptVerify {
out.close(); out.close();
byte[] clearText = out.toByteArray(); byte[] clearText = out.toByteArray();
outStream.write(clearText); mOutStream.write(clearText);
updateProgress(R.string.progress_processing_signature, 60, 100); updateProgress(R.string.progress_processing_signature, 60, 100);
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
if (sigList == null) { if (sigList == null) {
throw new PgpGeneralException(context.getString(R.string.error_corrupt_data)); throw new PgpGeneralException(mContext.getString(R.string.error_corrupt_data));
} }
PGPSignature signature = null; PGPSignature signature = null;
long signatureKeyId = 0; long signatureKeyId = 0;
PGPPublicKey signatureKey = null; PGPPublicKey signatureKey = null;
for (int i = 0; i < sigList.size(); ++i) { for (int i = 0; i < sigList.size(); ++i) {
signature = sigList.get(i); signature = sigList.get(i);
signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(context, signature.getKeyID()); signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
if (signatureKeyId == 0) { if (signatureKeyId == 0) {
signatureKeyId = signature.getKeyID(); signatureKeyId = signature.getKeyID();
} }
@ -579,7 +557,7 @@ public class PgpDecryptVerify {
} else { } else {
signatureKeyId = signature.getKeyID(); signatureKeyId = signature.getKeyID();
String userId = null; String userId = null;
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context, PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext,
signatureKeyId); signatureKeyId);
if (signKeyRing != null) { if (signKeyRing != null) {
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing)); userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
@ -623,7 +601,7 @@ public class PgpDecryptVerify {
} }
//Now check binding signatures //Now check binding signatures
boolean validKeyBinding = verifyKeyBinding(context, signature, signatureKey); boolean validKeyBinding = verifyKeyBinding(mContext, signature, signatureKey);
boolean validSignature = signature.verify(); boolean validSignature = signature.verify();
if (validSignature & validKeyBinding) { if (validSignature & validKeyBinding) {
@ -638,7 +616,8 @@ public class PgpDecryptVerify {
return returnData; return returnData;
} }
private static boolean verifyKeyBinding(Context context, PGPSignature signature, PGPPublicKey signatureKey) { private static boolean verifyKeyBinding(Context context,
PGPSignature signature, PGPPublicKey signatureKey) {
long signatureKeyId = signature.getKeyID(); long signatureKeyId = signature.getKeyID();
boolean validKeyBinding = false; boolean validKeyBinding = false;
@ -673,7 +652,8 @@ public class PgpDecryptVerify {
//about keys without subkey signing. Can't get it to import a slightly broken one //about keys without subkey signing. Can't get it to import a slightly broken one
//either, so we will err on bad subkey binding here. //either, so we will err on bad subkey binding here.
PGPSignature sig = itr.next(); PGPSignature sig = itr.next();
if (sig.getKeyID() == masterPublicKey.getKeyID() && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { if (sig.getKeyID() == masterPublicKey.getKeyID() &&
sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
//check and if ok, check primary key binding. //check and if ok, check primary key binding.
try { try {
sig.init(contentVerifierBuilderProvider, masterPublicKey); sig.init(contentVerifierBuilderProvider, masterPublicKey);
@ -684,34 +664,37 @@ public class PgpDecryptVerify {
continue; continue;
} }
if (validTempSubkeyBinding) if (validTempSubkeyBinding) {
validSubkeyBinding = true; validSubkeyBinding = true;
}
if (validTempSubkeyBinding) { if (validTempSubkeyBinding) {
validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(), validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(),
masterPublicKey, signingPublicKey); masterPublicKey, signingPublicKey);
if (validPrimaryKeyBinding) if (validPrimaryKeyBinding) {
break; break;
}
validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(), validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(),
masterPublicKey, signingPublicKey); masterPublicKey, signingPublicKey);
if (validPrimaryKeyBinding) if (validPrimaryKeyBinding) {
break; break;
}
} }
} }
} }
return (validSubkeyBinding & validPrimaryKeyBinding); return (validSubkeyBinding & validPrimaryKeyBinding);
} }
private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector Pkts, private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts,
PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) { PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
boolean validPrimaryKeyBinding = false; boolean validPrimaryKeyBinding = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider() new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureList eSigList; PGPSignatureList eSigList;
if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { if (pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) {
try { try {
eSigList = Pkts.getEmbeddedSignatures(); eSigList = pkts.getEmbeddedSignatures();
} catch (IOException e) { } catch (IOException e) {
return false; return false;
} catch (PGPException e) { } catch (PGPException e) {
@ -723,8 +706,9 @@ public class PgpDecryptVerify {
try { try {
emSig.init(contentVerifierBuilderProvider, signingPublicKey); emSig.init(contentVerifierBuilderProvider, signingPublicKey);
validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey); validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey);
if (validPrimaryKeyBinding) if (validPrimaryKeyBinding) {
break; break;
}
} catch (PGPException e) { } catch (PGPException e) {
continue; continue;
} catch (SignatureException e) { } catch (SignatureException e) {
@ -743,10 +727,9 @@ public class PgpDecryptVerify {
* @param sig * @param sig
* @param line * @param line
* @throws SignatureException * @throws SignatureException
* @throws IOException
*/ */
private static void processLine(PGPSignature sig, byte[] line) private static void processLine(PGPSignature sig, byte[] line)
throws SignatureException, IOException { throws SignatureException {
int length = getLengthWithoutWhiteSpace(line); int length = getLengthWithoutWhiteSpace(line);
if (length > 0) { if (length > 0) {
sig.update(line, 0, length); sig.update(line, 0, length);

View File

@ -19,36 +19,35 @@ package org.sufficientlysecure.keychain.pgp;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
public class PgpDecryptVerifyResult implements Parcelable { public class PgpDecryptVerifyResult implements Parcelable {
boolean symmetricPassphraseNeeded; boolean mSymmetricPassphraseNeeded;
boolean keyPassphraseNeeded; boolean mKeyPassphraseNeeded;
OpenPgpSignatureResult signatureResult; OpenPgpSignatureResult mSignatureResult;
public boolean isSymmetricPassphraseNeeded() { public boolean isSymmetricPassphraseNeeded() {
return symmetricPassphraseNeeded; return mSymmetricPassphraseNeeded;
} }
public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) { public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) {
this.symmetricPassphraseNeeded = symmetricPassphraseNeeded; this.mSymmetricPassphraseNeeded = symmetricPassphraseNeeded;
} }
public boolean isKeyPassphraseNeeded() { public boolean isKeyPassphraseNeeded() {
return keyPassphraseNeeded; return mKeyPassphraseNeeded;
} }
public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) { public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) {
this.keyPassphraseNeeded = keyPassphraseNeeded; this.mKeyPassphraseNeeded = keyPassphraseNeeded;
} }
public OpenPgpSignatureResult getSignatureResult() { public OpenPgpSignatureResult getSignatureResult() {
return signatureResult; return mSignatureResult;
} }
public void setSignatureResult(OpenPgpSignatureResult signatureResult) { public void setSignatureResult(OpenPgpSignatureResult signatureResult) {
this.signatureResult = signatureResult; this.mSignatureResult = signatureResult;
} }
public PgpDecryptVerifyResult() { public PgpDecryptVerifyResult() {
@ -56,9 +55,9 @@ public class PgpDecryptVerifyResult implements Parcelable {
} }
public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) { public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) {
this.symmetricPassphraseNeeded = b.symmetricPassphraseNeeded; this.mSymmetricPassphraseNeeded = b.mSymmetricPassphraseNeeded;
this.keyPassphraseNeeded = b.keyPassphraseNeeded; this.mKeyPassphraseNeeded = b.mKeyPassphraseNeeded;
this.signatureResult = b.signatureResult; this.mSignatureResult = b.mSignatureResult;
} }
@ -67,17 +66,17 @@ public class PgpDecryptVerifyResult implements Parcelable {
} }
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (symmetricPassphraseNeeded ? 1 : 0)); dest.writeByte((byte) (mSymmetricPassphraseNeeded ? 1 : 0));
dest.writeByte((byte) (keyPassphraseNeeded ? 1 : 0)); dest.writeByte((byte) (mKeyPassphraseNeeded ? 1 : 0));
dest.writeParcelable(signatureResult, 0); dest.writeParcelable(mSignatureResult, 0);
} }
public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() { public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() {
public PgpDecryptVerifyResult createFromParcel(final Parcel source) { public PgpDecryptVerifyResult createFromParcel(final Parcel source) {
PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult(); PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult();
vr.symmetricPassphraseNeeded = source.readByte() == 1; vr.mSymmetricPassphraseNeeded = source.readByte() == 1;
vr.keyPassphraseNeeded = source.readByte() == 1; vr.mKeyPassphraseNeeded = source.readByte() == 1;
vr.signatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader()); vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
return vr; return vr;
} }

View File

@ -17,22 +17,10 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.io.File; import android.content.Context;
import java.io.FileNotFoundException; import android.content.pm.PackageInfo;
import java.io.IOException; import android.content.pm.PackageManager.NameNotFoundException;
import java.io.InputStream; import org.spongycastle.openpgp.*;
import java.io.RandomAccessFile;
import java.security.SecureRandom;
import java.util.Iterator;
import java.util.regex.Pattern;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@ -42,21 +30,25 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.content.Context; import java.io.File;
import android.content.pm.PackageInfo; import java.io.IOException;
import android.content.pm.PackageManager.NameNotFoundException; import java.io.InputStream;
import java.io.RandomAccessFile;
import java.security.SecureRandom;
import java.util.Iterator;
import java.util.regex.Pattern;
public class PgpHelper { public class PgpHelper {
public static Pattern PGP_MESSAGE = Pattern.compile( public static final Pattern PGP_MESSAGE = Pattern.compile(
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL); ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
public static Pattern PGP_SIGNED_MESSAGE = Pattern public static final Pattern PGP_SIGNED_MESSAGE = Pattern
.compile( .compile(
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
Pattern.DOTALL); Pattern.DOTALL);
public static Pattern PGP_PUBLIC_KEY = Pattern.compile( public static final Pattern PGP_PUBLIC_KEY = Pattern.compile(
".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*",
Pattern.DOTALL); Pattern.DOTALL);
@ -187,17 +179,16 @@ public class PgpHelper {
/** /**
* Deletes file securely by overwriting it with random data before deleting it. * Deletes file securely by overwriting it with random data before deleting it.
* * <p/>
* TODO: Does this really help on flash storage? * TODO: Does this really help on flash storage?
* *
* @param context * @param context
* @param progress * @param progress
* @param file * @param file
* @throws FileNotFoundException
* @throws IOException * @throws IOException
*/ */
public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file) public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file)
throws FileNotFoundException, IOException { throws IOException {
long length = file.length(); long length = file.length();
SecureRandom random = new SecureRandom(); SecureRandom random = new SecureRandom();
RandomAccessFile raf = new RandomAccessFile(file, "rws"); RandomAccessFile raf = new RandomAccessFile(file, "rws");
@ -207,8 +198,9 @@ public class PgpHelper {
int pos = 0; int pos = 0;
String msg = context.getString(R.string.progress_deleting_securely, file.getName()); String msg = context.getString(R.string.progress_deleting_securely, file.getName());
while (pos < length) { while (pos < length) {
if (progress != null) if (progress != null) {
progress.setProgress(msg, (int) (100 * pos / length), 100); progress.setProgress(msg, (int) (100 * pos / length), 100);
}
random.nextBytes(data); random.nextBytes(data);
raf.write(data); raf.write(data);
pos += data.length; pos += data.length;

View File

@ -17,24 +17,11 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.io.BufferedInputStream; import android.content.Context;
import java.io.ByteArrayOutputStream; import android.os.Bundle;
import java.io.FileNotFoundException; import android.os.Environment;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
@ -43,28 +30,37 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.HkpKeyServer; import org.sufficientlysecure.keychain.util.*;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException; import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.content.Context; import java.io.ByteArrayOutputStream;
import android.os.Bundle; import java.io.IOException;
import android.os.Environment; import java.io.OutputStream;
import java.security.Provider;
import java.util.ArrayList;
import java.util.List;
public class PgpImportExport { public class PgpImportExport {
private Context mContext; private Context mContext;
private ProgressDialogUpdater mProgress; private ProgressDialogUpdater mProgress;
private KeychainServiceListener mKeychainServiceListener;
public PgpImportExport(Context context, ProgressDialogUpdater progress) { public PgpImportExport(Context context, ProgressDialogUpdater progress) {
super(); super();
this.mContext = context; this.mContext = context;
this.mProgress = progress; this.mProgress = progress;
} }
public PgpImportExport(Context context,
ProgressDialogUpdater progress, KeychainServiceListener keychainListener) {
super();
this.mContext = context;
this.mProgress = progress;
this.mKeychainServiceListener = keychainListener;
}
public void updateProgress(int message, int current, int total) { public void updateProgress(int message, int current, int total) {
if (mProgress != null) { if (mProgress != null) {
mProgress.setProgress(message, current, total); mProgress.setProgress(message, current, total);
@ -85,8 +81,9 @@ public class PgpImportExport {
public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos); ArmoredOutputStream aos = null;
try { try {
aos = new ArmoredOutputStream(bos);
aos.write(keyring.getEncoded()); aos.write(keyring.getEncoded());
aos.close(); aos.close();
@ -101,7 +98,8 @@ public class PgpImportExport {
return false; return false;
} finally { } finally {
try { try {
bos.close(); if (aos != null) { aos.close(); }
if (bos != null) { bos.close(); }
} catch (IOException e) { } catch (IOException e) {
} }
} }
@ -161,59 +159,68 @@ public class PgpImportExport {
return returnData; return returnData;
} }
public Bundle exportKeyRings(ArrayList<Long> keyRingMasterKeyIds, int keyType, public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds, ArrayList<Long> secretKeyRingMasterIds,
OutputStream outStream) throws PgpGeneralException, FileNotFoundException, OutputStream outStream) throws PgpGeneralException,
PGPException, IOException { PGPException, IOException {
Bundle returnData = new Bundle(); Bundle returnData = new Bundle();
int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size();
int progress = 0;
updateProgress( updateProgress(
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
keyRingMasterKeyIds.size()), 0, 100); masterKeyIdsSize), 0, 100);
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
throw new PgpGeneralException( throw new PgpGeneralException(
mContext.getString(R.string.error_external_storage_not_ready)); mContext.getString(R.string.error_external_storage_not_ready));
} }
// For each public masterKey id
for (long pubKeyMasterId : publicKeyRingMasterIds) {
progress++;
// Create an output stream
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
if (keyType == Id.type.secret_key) { updateProgress(progress * 100 / masterKeyIdsSize, 100);
ArmoredOutputStream outSec = new ArmoredOutputStream(outStream); PGPPublicKeyRing publicKeyRing =
outSec.setHeader("Version", PgpHelper.getFullVersion(mContext)); ProviderHelper.getPGPPublicKeyRingByMasterKeyId(mContext, pubKeyMasterId);
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) { if (publicKeyRing != null) {
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100); publicKeyRing.encode(arOutStream);
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
mContext, keyRingMasterKeyIds.get(i));
if (secretKeyRing != null) {
secretKeyRing.encode(outSec);
}
} }
outSec.close();
} else {
// export public keyrings...
ArmoredOutputStream outPub = new ArmoredOutputStream(outStream);
outPub.setHeader("Version", PgpHelper.getFullVersion(mContext));
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) { if (mKeychainServiceListener.hasServiceStopped()) {
// double the needed time if exporting both public and secret parts arOutStream.close();
if (keyType == Id.type.secret_key) { return null;
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
} else {
updateProgress(i * 100 / keyRingMasterKeyIds.size(), 100);
}
PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(
mContext, keyRingMasterKeyIds.get(i));
if (publicKeyRing != null) {
publicKeyRing.encode(outPub);
}
} }
outPub.close();
arOutStream.close();
} }
returnData.putInt(KeychainIntentService.RESULT_EXPORT, keyRingMasterKeyIds.size()); // For each secret masterKey id
for (long secretKeyMasterId : secretKeyRingMasterIds) {
progress++;
// Create an output stream
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
updateProgress(progress * 100 / masterKeyIdsSize, 100);
PGPSecretKeyRing secretKeyRing =
ProviderHelper.getPGPSecretKeyRingByMasterKeyId(mContext, secretKeyMasterId);
if (secretKeyRing != null) {
secretKeyRing.encode(arOutStream);
}
if (mKeychainServiceListener.hasServiceStopped()) {
arOutStream.close();
return null;
}
arOutStream.close();
}
returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize);
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
@ -234,7 +241,7 @@ public class PgpImportExport {
for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>( for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>(
secretKeyRing.getSecretKeys())) { secretKeyRing.getSecretKeys())) {
if (!testSecretKey.isMasterKey()) { if (!testSecretKey.isMasterKey()) {
if (PgpKeyHelper.isSecretKeyPrivateEmpty(testSecretKey)) { if (testSecretKey.isPrivateKeyEmpty()) {
// this is bad, something is very wrong... // this is bad, something is very wrong...
save = false; save = false;
status = Id.return_value.bad; status = Id.return_value.bad;
@ -255,8 +262,9 @@ public class PgpImportExport {
} }
newPubRing = PGPPublicKeyRing.insertPublicKey(newPubRing, key); newPubRing = PGPPublicKeyRing.insertPublicKey(newPubRing, key);
} }
if (newPubRing != null) if (newPubRing != null) {
ProviderHelper.saveKeyRing(mContext, newPubRing); ProviderHelper.saveKeyRing(mContext, newPubRing);
}
// TODO: remove status returns, use exceptions! // TODO: remove status returns, use exceptions!
status = Id.return_value.ok; status = Id.return_value.ok;
} }

View File

@ -17,28 +17,27 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.util.Calendar; import android.content.Context;
import java.util.Date; import android.graphics.Color;
import java.util.GregorianCalendar; import android.text.Spannable;
import java.util.Locale; import android.text.SpannableStringBuilder;
import java.util.Vector; import android.text.style.ForegroundColorSpan;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.util.encoders.Hex;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.content.Context; import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PgpKeyHelper { public class PgpKeyHelper {
@ -466,57 +465,32 @@ public class PgpKeyHelper {
String algorithmStr; String algorithmStr;
switch (algorithm) { switch (algorithm) {
case PGPPublicKey.RSA_ENCRYPT: case PGPPublicKey.RSA_ENCRYPT:
case PGPPublicKey.RSA_GENERAL: case PGPPublicKey.RSA_GENERAL:
case PGPPublicKey.RSA_SIGN: { case PGPPublicKey.RSA_SIGN: {
algorithmStr = "RSA"; algorithmStr = "RSA";
break; break;
} }
case PGPPublicKey.DSA: { case PGPPublicKey.DSA: {
algorithmStr = "DSA"; algorithmStr = "DSA";
break; break;
} }
case PGPPublicKey.ELGAMAL_ENCRYPT: case PGPPublicKey.ELGAMAL_ENCRYPT:
case PGPPublicKey.ELGAMAL_GENERAL: { case PGPPublicKey.ELGAMAL_GENERAL: {
algorithmStr = "ElGamal"; algorithmStr = "ElGamal";
break; break;
} }
default: { default: {
algorithmStr = "Unknown"; algorithmStr = "Unknown";
break; break;
} }
} }
return algorithmStr + ", " + keySize + " bit"; return algorithmStr + ", " + keySize + " bit";
} }
/**
* Converts fingerprint to hex with whitespaces after 4 characters
*
* @param fp
* @return
*/
public static String convertFingerprintToHex(byte[] fp, boolean chunked) {
String fingerPrint = "";
for (int i = 0; i < fp.length; ++i) {
if (chunked && i != 0 && i % 10 == 0) {
fingerPrint += " ";
} else if (chunked && i != 0 && i % 2 == 0) {
fingerPrint += " ";
}
String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(Locale.US);
while (chunk.length() < 2) {
chunk = "0" + chunk;
}
fingerPrint += chunk;
}
return fingerPrint;
}
public static String getFingerPrint(Context context, long keyId) { public static String getFingerPrint(Context context, long keyId) {
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId); PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
// if it is no public key get it from your own keys... // if it is no public key get it from your own keys...
@ -529,45 +503,140 @@ public class PgpKeyHelper {
key = secretKey.getPublicKey(); key = secretKey.getPublicKey();
} }
return convertFingerprintToHex(key.getFingerprint(), true); return convertFingerprintToHex(key.getFingerprint());
}
public static boolean isSecretKeyPrivateEmpty(PGPSecretKey secretKey) {
return secretKey.isPrivateKeyEmpty();
}
// public static boolean isSecretKeyPrivateEmpty(Context context, long keyId) {
// PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
// if (secretKey == null) {
// Log.e(Constants.TAG, "Key could not be found!");
// return false; // could be a public key, assume it is not empty
// }
// return isSecretKeyPrivateEmpty(secretKey);
// }
public static String convertKeyIdToHex(long keyId) {
String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(Locale.US);
while (fingerPrint.length() < 8) {
fingerPrint = "0" + fingerPrint;
}
return fingerPrint;
} }
/** /**
* TODO: documentation * Converts fingerprint to hex (optional: with whitespaces after 4 characters)
* <p/>
* Fingerprint is shown using lowercase characters. Studies have shown that humans can
* better differentiate between numbers and letters when letters are lowercase.
*
* @param fingerprint
* @param split split into 4 character chunks
* @return
*/
public static String convertFingerprintToHex(byte[] fingerprint) {
String hexString = Hex.toHexString(fingerprint);
return hexString;
}
/**
* Convert key id from long to 64 bit hex string
* <p/>
* V4: "The Key ID is the low-order 64 bits of the fingerprint"
* <p/>
* see http://tools.ietf.org/html/rfc4880#section-12.2
* *
* @param keyId * @param keyId
* @return * @return
*/ */
public static String convertKeyToHex(long keyId) { public static String convertKeyIdToHex(long keyId) {
return convertKeyIdToHex(keyId >> 32) + convertKeyIdToHex(keyId); long upper = keyId >> 32;
if (upper == 0) {
// this is a short key id
return convertKeyIdToHexShort(keyId);
}
return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
} }
public static long convertHexToKeyId(String data) { public static String convertKeyIdToHexShort(long keyId) {
int len = data.length(); return "0x" + convertKeyIdToHex32bit(keyId);
String s2 = data.substring(len - 8); }
String s1 = data.substring(0, len - 8);
return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); private static String convertKeyIdToHex32bit(long keyId) {
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
while (hexString.length() < 8) {
hexString = "0" + hexString;
}
return hexString;
}
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
// split by 4 characters
fingerprint = fingerprint.replaceAll("(.{4})(?!$)", "$1 ");
// add line breaks to have a consistent "image" that can be recognized
char[] chars = fingerprint.toCharArray();
chars[24] = '\n';
fingerprint = String.valueOf(chars);
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
try {
// for each 4 characters of the fingerprint + 1 space
for (int i = 0; i < fingerprint.length(); i += 5) {
int spanEnd = Math.min(i + 4, fingerprint.length());
String fourChars = fingerprint.substring(i, spanEnd);
int raw = Integer.parseInt(fourChars, 16);
byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)};
int[] color = getRgbForData(bytes);
int r = color[0];
int g = color[1];
int b = color[2];
// we cannot change black by multiplication, so adjust it to an almost-black grey,
// which will then be brightened to the minimal brightness level
if (r == 0 && g == 0 && b == 0) {
r = 1;
g = 1;
b = 1;
}
// Convert rgb to brightness
double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// If a color is too dark to be seen on black,
// then brighten it up to a minimal brightness.
if (brightness < 80) {
double factor = 80.0 / brightness;
r = Math.min(255, (int) (r * factor));
g = Math.min(255, (int) (g * factor));
b = Math.min(255, (int) (b * factor));
// If it is too light, then darken it to a respective maximal brightness.
} else if (brightness > 180) {
double factor = 180.0 / brightness;
r = (int) (r * factor);
g = (int) (g * factor);
b = (int) (b * factor);
}
// Create a foreground color with the 3 digest integers as RGB
// and then converting that int to hex to use as a color
sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)),
i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
} catch (Exception e) {
Log.e(Constants.TAG, "Colorization failed", e);
// if anything goes wrong, then just display the fingerprint without colour,
// instead of partially correct colour or wrong colours
return new SpannableStringBuilder(fingerprint);
}
return sb;
}
/**
* Converts the given bytes to a unique RGB color using SHA1 algorithm
*
* @param bytes
* @return an integer array containing 3 numeric color representations (Red, Green, Black)
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.DigestException
*/
private static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(bytes);
byte[] digest = md.digest();
int[] result = {((int) digest[0] + 256) % 256,
((int) digest[1] + 256) % 256,
((int) digest[2] + 256) % 256};
return result;
} }
/** /**
@ -577,7 +646,7 @@ public class PgpKeyHelper {
* @return array with naming (0), email (1), comment (2) * @return array with naming (0), email (1), comment (2)
*/ */
public static String[] splitUserId(String userId) { public static String[] splitUserId(String userId) {
String[] result = new String[] { null, null, null }; String[] result = new String[]{null, null, null};
if (userId == null || userId.equals("")) { if (userId == null || userId.equals("")) {
return result; return result;
@ -598,7 +667,6 @@ public class PgpKeyHelper {
result[0] = matcher.group(1); result[0] = matcher.group(1);
result[1] = matcher.group(3); result[1] = matcher.group(3);
result[2] = matcher.group(2); result[2] = matcher.group(2);
return result;
} }
return result; return result;

View File

@ -30,35 +30,19 @@ import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Iterator; import java.util.Iterator;
import java.util.TimeZone; import java.util.TimeZone;
import android.content.Context;
import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec; import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyPair;
import org.spongycastle.openpgp.PGPKeyRingGenerator;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.PGPUtil; import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PGPDigestCalculator; import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.*;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@ -70,20 +54,34 @@ import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.content.Context; import android.content.Context;
import android.util.Pair; import android.util.Pair;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Primes;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
public class PgpKeyOperation { public class PgpKeyOperation {
private final Context mContext; private final Context mContext;
private final ProgressDialogUpdater mProgress; private final ProgressDialogUpdater mProgress;
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[] { private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5, SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5,
SymmetricKeyAlgorithmTags.TRIPLE_DES }; SymmetricKeyAlgorithmTags.TRIPLE_DES};
private static final int[] PREFERRED_HASH_ALGORITHMS = new int[] { HashAlgorithmTags.SHA1, private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{HashAlgorithmTags.SHA1,
HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160 }; HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160};
private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[] { private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
CompressionAlgorithmTags.ZIP }; CompressionAlgorithmTags.ZIP};
public PgpKeyOperation(Context context, ProgressDialogUpdater progress) { public PgpKeyOperation(Context context, ProgressDialogUpdater progress) {
super(); super();
@ -104,8 +102,9 @@ public class PgpKeyOperation {
} }
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase, public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, boolean isMasterKey)
PgpGeneralException, InvalidAlgorithmParameterException { throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
PgpGeneralException, InvalidAlgorithmParameterException {
if (keySize < 512) { if (keySize < 512) {
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit)); throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
@ -119,41 +118,41 @@ public class PgpKeyOperation {
KeyPairGenerator keyGen; KeyPairGenerator keyGen;
switch (algorithmChoice) { switch (algorithmChoice) {
case Id.choice.algorithm.dsa: { case Id.choice.algorithm.dsa: {
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
keyGen.initialize(keySize, new SecureRandom()); keyGen.initialize(keySize, new SecureRandom());
algorithm = PGPPublicKey.DSA; algorithm = PGPPublicKey.DSA;
break; break;
}
case Id.choice.algorithm.elgamal: {
if (isMasterKey) {
throw new PgpGeneralException(
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
} }
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
BigInteger p = Primes.getBestPrime(keySize);
BigInteger g = new BigInteger("2");
ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); case Id.choice.algorithm.elgamal: {
if (isMasterKey) {
throw new PgpGeneralException(
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
}
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
BigInteger p = Primes.getBestPrime(keySize);
BigInteger g = new BigInteger("2");
keyGen.initialize(elParams); ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
algorithm = PGPPublicKey.ELGAMAL_ENCRYPT;
break;
}
case Id.choice.algorithm.rsa: { keyGen.initialize(elParams);
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); algorithm = PGPPublicKey.ELGAMAL_ENCRYPT;
keyGen.initialize(keySize, new SecureRandom()); break;
}
algorithm = PGPPublicKey.RSA_GENERAL; case Id.choice.algorithm.rsa: {
break; keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
} keyGen.initialize(keySize, new SecureRandom());
default: { algorithm = PGPPublicKey.RSA_GENERAL;
throw new PgpGeneralException( break;
mContext.getString(R.string.error_unknown_algorithm_choice)); }
}
default: {
throw new PgpGeneralException(
mContext.getString(R.string.error_unknown_algorithm_choice));
}
} }
// build new key pair // build new key pair
@ -170,11 +169,10 @@ public class PgpKeyOperation {
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor); sha1Calc, isMasterKey, keyEncryptor);
} }
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
String newPassPhrase) throws IOException, PGPException { String newPassPhrase) throws IOException, PGPException {
updateProgress(R.string.progress_building_key, 0, 100); updateProgress(R.string.progress_building_key, 0, 100);
if (oldPassPhrase == null) { if (oldPassPhrase == null) {
@ -221,15 +219,15 @@ public class PgpKeyOperation {
updateProgress(R.string.progress_certifying_master_key, 20, 100); updateProgress(R.string.progress_certifying_master_key, 20, 100);
int user_id_index = 0; int user_id_index = 0;
for (String userId : userIds) { for (String userId : userIds) {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
user_id_index++; user_id_index++;
} }
@ -280,7 +278,7 @@ public class PgpKeyOperation {
updateProgress(R.string.progress_adding_sub_keys, 40, 100); updateProgress(R.string.progress_adding_sub_keys, 40, 100);
for (int i = 1; i < keys.size(); ++i) { for (int i = 1; i < keys.size(); ++i) {
updateProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100); updateProgress(40 + 40 * (i - 1) / (keys.size() - 1), 100);
PGPSecretKey subKey = keys.get(i); PGPSecretKey subKey = keys.get(i);
PGPPublicKey subPublicKey = subKey.getPublicKey(); PGPPublicKey subPublicKey = subKey.getPublicKey();
@ -345,7 +343,7 @@ public class PgpKeyOperation {
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
} }
public void buildSecretKey(SaveKeyringParcel saveParcel) throws PgpGeneralException, public void buildSecretKey (SaveKeyringParcel saveParcel)throws PgpGeneralException,
PGPException, SignatureException, IOException { PGPException, SignatureException, IOException {
updateProgress(R.string.progress_building_key, 0, 100); updateProgress(R.string.progress_building_key, 0, 100);
@ -387,7 +385,7 @@ public class PgpKeyOperation {
Todo Todo
identify more things which need to be preserved - e.g. trust levels? identify more things which need to be preserved - e.g. trust levels?
user attributes user attributes
*/ */
if (saveParcel.deletedKeys != null) { if (saveParcel.deletedKeys != null) {
for (PGPSecretKey dKey : saveParcel.deletedKeys) { for (PGPSecretKey dKey : saveParcel.deletedKeys) {
@ -546,7 +544,7 @@ public class PgpKeyOperation {
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
for (int i = 1; i < saveParcel.keys.size(); ++i) { for (int i = 1; i < saveParcel.keys.size(); ++i) {
updateProgress(40 + 50 * i/ saveParcel.keys.size(), 100); updateProgress(40 + 50 * i / saveParcel.keys.size(), 100);
if (saveParcel.moddedKeys[i]) { if (saveParcel.moddedKeys[i]) {
PGPSecretKey subKey = saveParcel.keys.get(i); PGPSecretKey subKey = saveParcel.keys.get(i);
PGPPublicKey subPublicKey = subKey.getPublicKey(); PGPPublicKey subPublicKey = subKey.getPublicKey();
@ -558,8 +556,8 @@ public class PgpKeyOperation {
"".toCharArray()); "".toCharArray());
} else { } else {
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.oldPassPhrase.toCharArray()); saveParcel.oldPassPhrase.toCharArray());
} }
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2); PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
@ -642,50 +640,80 @@ public class PgpKeyOperation {
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew); mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
updateProgress(R.string.progress_saving_key_ring, 90, 100); updateProgress(R.string.progress_saving_key_ring, 90, 100);
/* additional handy debug info
Log.d(Constants.TAG, " ------- in private key -------");
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
Log.d(Constants.TAG, " ------- in public key -------");
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
*/
ProviderHelper.saveKeyRing(mContext, mKR); ProviderHelper.saveKeyRing(mContext, mKR);
ProviderHelper.saveKeyRing(mContext, pKR); ProviderHelper.saveKeyRing(mContext, pKR);
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
} }
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase) public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
throws PgpGeneralException, PGPException, SignatureException { throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
if (passphrase == null) { if (passphrase == null) {
throw new PgpGeneralException("Unable to obtain passphrase"); throw new PgpGeneralException("Unable to obtain passphrase");
} else { } else {
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator; {
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
if (certificationKey == null) {
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new PgpGeneralException(
mContext.getString(R.string.error_could_not_extract_private_key));
}
// TODO: SHA256 fixed?
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
}
{ // supply signatureGenerator with a SubpacketVector
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
}
// fetch public key ring, add the certification and return it
PGPPublicKeyRing pubring = ProviderHelper PGPPublicKeyRing pubring = ProviderHelper
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId); .getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId);
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); for(String userId : new IterableIterator<String>(userIds.iterator())) {
if (certificationKey == null) { PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey);
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); signedKey = PGPPublicKey.addCertification(signedKey, userId, sig);
} }
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) {
throw new PgpGeneralException(
mContext.getString(R.string.error_could_not_extract_private_key));
}
// TODO: SHA256 fixed?
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
contentSignerBuilder);
signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey);
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketVector packetVector = spGen.generate();
signatureGenerator.setHashedSubpackets(packetVector);
PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId),
signatureGenerator.generate());
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey); pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
return pubring; return pubring;

View File

@ -18,28 +18,11 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import android.content.Context; import android.content.Context;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream; import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.openpgp.PGPCompressedDataGenerator; import org.spongycastle.openpgp.*;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
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.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.*;
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
@ -49,11 +32,7 @@ import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import java.security.SignatureException; import java.security.SignatureException;
@ -63,110 +42,110 @@ import java.util.Date;
* This class uses a Builder pattern! * This class uses a Builder pattern!
*/ */
public class PgpSignEncrypt { public class PgpSignEncrypt {
private Context context; private Context mContext;
private InputData data; private InputData mData;
private OutputStream outStream; private OutputStream mOutStream;
private ProgressDialogUpdater progress; private ProgressDialogUpdater mProgress;
private boolean enableAsciiArmorOutput; private boolean mEnableAsciiArmorOutput;
private int compressionId; private int mCompressionId;
private long[] encryptionKeyIds; private long[] mEncryptionKeyIds;
private String encryptionPassphrase; private String mEncryptionPassphrase;
private int symmetricEncryptionAlgorithm; private int mSymmetricEncryptionAlgorithm;
private long signatureKeyId; private long mSignatureKeyId;
private int signatureHashAlgorithm; private int mSignatureHashAlgorithm;
private boolean signatureForceV3; private boolean mSignatureForceV3;
private String signaturePassphrase; private String mSignaturePassphrase;
private PgpSignEncrypt(Builder builder) { private PgpSignEncrypt(Builder builder) {
// private Constructor can only be called from Builder // private Constructor can only be called from Builder
this.context = builder.context; this.mContext = builder.mContext;
this.data = builder.data; this.mData = builder.mData;
this.outStream = builder.outStream; this.mOutStream = builder.mOutStream;
this.progress = builder.progress; this.mProgress = builder.mProgress;
this.enableAsciiArmorOutput = builder.enableAsciiArmorOutput; this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
this.compressionId = builder.compressionId; this.mCompressionId = builder.mCompressionId;
this.encryptionKeyIds = builder.encryptionKeyIds; this.mEncryptionKeyIds = builder.mEncryptionKeyIds;
this.encryptionPassphrase = builder.encryptionPassphrase; this.mEncryptionPassphrase = builder.mEncryptionPassphrase;
this.symmetricEncryptionAlgorithm = builder.symmetricEncryptionAlgorithm; this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
this.signatureKeyId = builder.signatureKeyId; this.mSignatureKeyId = builder.mSignatureKeyId;
this.signatureHashAlgorithm = builder.signatureHashAlgorithm; this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
this.signatureForceV3 = builder.signatureForceV3; this.mSignatureForceV3 = builder.mSignatureForceV3;
this.signaturePassphrase = builder.signaturePassphrase; this.mSignaturePassphrase = builder.mSignaturePassphrase;
} }
public static class Builder { public static class Builder {
// mandatory parameter // mandatory parameter
private Context context; private Context mContext;
private InputData data; private InputData mData;
private OutputStream outStream; private OutputStream mOutStream;
// optional // optional
private ProgressDialogUpdater progress = null; private ProgressDialogUpdater mProgress = null;
private boolean enableAsciiArmorOutput = false; private boolean mEnableAsciiArmorOutput = false;
private int compressionId = Id.choice.compression.none; private int mCompressionId = Id.choice.compression.none;
private long[] encryptionKeyIds = new long[0]; private long[] mEncryptionKeyIds = new long[0];
private String encryptionPassphrase = null; private String mEncryptionPassphrase = null;
private int symmetricEncryptionAlgorithm = 0; private int mSymmetricEncryptionAlgorithm = 0;
private long signatureKeyId = Id.key.none; private long mSignatureKeyId = Id.key.none;
private int signatureHashAlgorithm = 0; private int mSignatureHashAlgorithm = 0;
private boolean signatureForceV3 = false; private boolean mSignatureForceV3 = false;
private String signaturePassphrase = null; private String mSignaturePassphrase = null;
public Builder(Context context, InputData data, OutputStream outStream) { public Builder(Context context, InputData data, OutputStream outStream) {
this.context = context; this.mContext = context;
this.data = data; this.mData = data;
this.outStream = outStream; this.mOutStream = outStream;
} }
public Builder progress(ProgressDialogUpdater progress) { public Builder progress(ProgressDialogUpdater progress) {
this.progress = progress; this.mProgress = progress;
return this; return this;
} }
public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) { public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
this.enableAsciiArmorOutput = enableAsciiArmorOutput; this.mEnableAsciiArmorOutput = enableAsciiArmorOutput;
return this; return this;
} }
public Builder compressionId(int compressionId) { public Builder compressionId(int compressionId) {
this.compressionId = compressionId; this.mCompressionId = compressionId;
return this; return this;
} }
public Builder encryptionKeyIds(long[] encryptionKeyIds) { public Builder encryptionKeyIds(long[] encryptionKeyIds) {
this.encryptionKeyIds = encryptionKeyIds; this.mEncryptionKeyIds = encryptionKeyIds;
return this; return this;
} }
public Builder encryptionPassphrase(String encryptionPassphrase) { public Builder encryptionPassphrase(String encryptionPassphrase) {
this.encryptionPassphrase = encryptionPassphrase; this.mEncryptionPassphrase = encryptionPassphrase;
return this; return this;
} }
public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
this.symmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; this.mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
return this; return this;
} }
public Builder signatureKeyId(long signatureKeyId) { public Builder signatureKeyId(long signatureKeyId) {
this.signatureKeyId = signatureKeyId; this.mSignatureKeyId = signatureKeyId;
return this; return this;
} }
public Builder signatureHashAlgorithm(int signatureHashAlgorithm) { public Builder signatureHashAlgorithm(int signatureHashAlgorithm) {
this.signatureHashAlgorithm = signatureHashAlgorithm; this.mSignatureHashAlgorithm = signatureHashAlgorithm;
return this; return this;
} }
public Builder signatureForceV3(boolean signatureForceV3) { public Builder signatureForceV3(boolean signatureForceV3) {
this.signatureForceV3 = signatureForceV3; this.mSignatureForceV3 = signatureForceV3;
return this; return this;
} }
public Builder signaturePassphrase(String signaturePassphrase) { public Builder signaturePassphrase(String signaturePassphrase) {
this.signaturePassphrase = signaturePassphrase; this.mSignaturePassphrase = signaturePassphrase;
return this; return this;
} }
@ -176,14 +155,14 @@ public class PgpSignEncrypt {
} }
public void updateProgress(int message, int current, int total) { public void updateProgress(int message, int current, int total) {
if (progress != null) { if (mProgress != null) {
progress.setProgress(message, current, total); mProgress.setProgress(message, current, total);
} }
} }
public void updateProgress(int current, int total) { public void updateProgress(int current, int total) {
if (progress != null) { if (mProgress != null) {
progress.setProgress(current, total); mProgress.setProgress(current, total);
} }
} }
@ -201,17 +180,17 @@ public class PgpSignEncrypt {
throws IOException, PgpGeneralException, PGPException, NoSuchProviderException, throws IOException, PgpGeneralException, PGPException, NoSuchProviderException,
NoSuchAlgorithmException, SignatureException { NoSuchAlgorithmException, SignatureException {
boolean enableSignature = signatureKeyId != Id.key.none; boolean enableSignature = mSignatureKeyId != Id.key.none;
boolean enableEncryption = (encryptionKeyIds.length != 0 || encryptionPassphrase != null); boolean enableEncryption = (mEncryptionKeyIds.length != 0 || mEncryptionPassphrase != null);
boolean enableCompression = (enableEncryption && compressionId != Id.choice.compression.none); boolean enableCompression = (enableEncryption && mCompressionId != Id.choice.compression.none);
Log.d(Constants.TAG, "enableSignature:" + enableSignature Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ "\nenableEncryption:" + enableEncryption + "\nenableEncryption:" + enableEncryption
+ "\nenableCompression:" + enableCompression + "\nenableCompression:" + enableCompression
+ "\nenableAsciiArmorOutput:" + enableAsciiArmorOutput); + "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput);
int signatureType; int signatureType;
if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { if (mEnableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
// for sign-only ascii text // for sign-only ascii text
signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT; signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
} else { } else {
@ -220,12 +199,12 @@ public class PgpSignEncrypt {
ArmoredOutputStream armorOut = null; ArmoredOutputStream armorOut = null;
OutputStream out; OutputStream out;
if (enableAsciiArmorOutput) { if (mEnableAsciiArmorOutput) {
armorOut = new ArmoredOutputStream(outStream); armorOut = new ArmoredOutputStream(mOutStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(context)); armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
out = armorOut; out = armorOut;
} else { } else {
out = outStream; out = mOutStream;
} }
/* Get keys for signature generation for later usage */ /* Get keys for signature generation for later usage */
@ -233,25 +212,25 @@ public class PgpSignEncrypt {
PGPSecretKeyRing signingKeyRing = null; PGPSecretKeyRing signingKeyRing = null;
PGPPrivateKey signaturePrivateKey = null; PGPPrivateKey signaturePrivateKey = null;
if (enableSignature) { if (enableSignature) {
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId); signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
if (signingKey == null) { if (signingKey == null) {
throw new PgpGeneralException(context.getString(R.string.error_signature_failed)); throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
} }
if (signaturePassphrase == null) { if (mSignaturePassphrase == null) {
throw new PgpGeneralException( throw new PgpGeneralException(
context.getString(R.string.error_no_signature_passphrase)); mContext.getString(R.string.error_no_signature_passphrase));
} }
updateProgress(R.string.progress_extracting_signature_key, 0, 100); updateProgress(R.string.progress_extracting_signature_key, 0, 100);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mSignaturePassphrase.toCharArray());
signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) { if (signaturePrivateKey == null) {
throw new PgpGeneralException( throw new PgpGeneralException(
context.getString(R.string.error_could_not_extract_private_key)); mContext.getString(R.string.error_could_not_extract_private_key));
} }
} }
updateProgress(R.string.progress_preparing_streams, 5, 100); updateProgress(R.string.progress_preparing_streams, 5, 100);
@ -261,23 +240,23 @@ public class PgpSignEncrypt {
if (enableEncryption) { if (enableEncryption) {
// has Integrity packet enabled! // has Integrity packet enabled!
JcePGPDataEncryptorBuilder encryptorBuilder = JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm) new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
.setWithIntegrityPacket(true); .setWithIntegrityPacket(true);
cPk = new PGPEncryptedDataGenerator(encryptorBuilder); cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
if (encryptionKeyIds.length == 0) { if (mEncryptionKeyIds.length == 0) {
// Symmetric encryption // Symmetric encryption
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption"); Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator = JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray()); new JcePBEKeyEncryptionMethodGenerator(mEncryptionPassphrase.toCharArray());
cPk.addMethod(symmetricEncryptionGenerator); cPk.addMethod(symmetricEncryptionGenerator);
} else { } else {
// Asymmetric encryption // Asymmetric encryption
for (long id : encryptionKeyIds) { for (long id : mEncryptionKeyIds) {
PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(context, id); PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(mContext, id);
if (key != null) { if (key != null) {
JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator = JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator =
new JcePublicKeyKeyEncryptionMethodGenerator(key); new JcePublicKeyKeyEncryptionMethodGenerator(key);
@ -295,10 +274,10 @@ public class PgpSignEncrypt {
// content signer based on signing key algorithm and chosen hash algorithm // content signer based on signing key algorithm and chosen hash algorithm
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm) signingKey.getPublicKey().getAlgorithm(), mSignatureHashAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
signatureV3Generator.init(signatureType, signaturePrivateKey); signatureV3Generator.init(signatureType, signaturePrivateKey);
} else { } else {
@ -322,14 +301,14 @@ public class PgpSignEncrypt {
encryptionOut = cPk.open(out, new byte[1 << 16]); encryptionOut = cPk.open(out, new byte[1 << 16]);
if (enableCompression) { if (enableCompression) {
compressGen = new PGPCompressedDataGenerator(compressionId); compressGen = new PGPCompressedDataGenerator(mCompressionId);
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut)); bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
} else { } else {
bcpgOut = new BCPGOutputStream(encryptionOut); bcpgOut = new BCPGOutputStream(encryptionOut);
} }
if (enableSignature) { if (enableSignature) {
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
} else { } else {
signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
@ -345,13 +324,13 @@ public class PgpSignEncrypt {
long progress = 0; long progress = 0;
int n; int n;
byte[] buffer = new byte[1 << 16]; byte[] buffer = new byte[1 << 16];
InputStream in = data.getInputStream(); InputStream in = mData.getInputStream();
while ((n = in.read(buffer)) > 0) { while ((n = in.read(buffer)) > 0) {
pOut.write(buffer, 0, n); pOut.write(buffer, 0, n);
// update signature buffer if signature is requested // update signature buffer if signature is requested
if (enableSignature) { if (enableSignature) {
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator.update(buffer, 0, n); signatureV3Generator.update(buffer, 0, n);
} else { } else {
signatureGenerator.update(buffer, 0, n); signatureGenerator.update(buffer, 0, n);
@ -359,26 +338,26 @@ public class PgpSignEncrypt {
} }
progress += n; progress += n;
if (data.getSize() != 0) { if (mData.getSize() != 0) {
updateProgress((int) (20 + (95 - 20) * progress / data.getSize()), 100); updateProgress((int) (20 + (95 - 20) * progress / mData.getSize()), 100);
} }
} }
literalGen.close(); literalGen.close();
} else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) { } else if (mEnableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
/* sign-only of ascii text */ /* sign-only of ascii text */
updateProgress(R.string.progress_signing, 40, 100); updateProgress(R.string.progress_signing, 40, 100);
// write directly on armor output stream // write directly on armor output stream
armorOut.beginClearText(signatureHashAlgorithm); armorOut.beginClearText(mSignatureHashAlgorithm);
InputStream in = data.getInputStream(); InputStream in = mData.getInputStream();
final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
final byte[] newline = "\r\n".getBytes("UTF-8"); final byte[] newline = "\r\n".getBytes("UTF-8");
if (signatureForceV3) { if (mSignatureForceV3) {
processLine(reader.readLine(), armorOut, signatureV3Generator); processLine(reader.readLine(), armorOut, signatureV3Generator);
} else { } else {
processLine(reader.readLine(), armorOut, signatureGenerator); processLine(reader.readLine(), armorOut, signatureGenerator);
@ -395,7 +374,7 @@ public class PgpSignEncrypt {
armorOut.write(newline); armorOut.write(newline);
// update signature buffer with input line // update signature buffer with input line
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator.update(newline); signatureV3Generator.update(newline);
processLine(line, armorOut, signatureV3Generator); processLine(line, armorOut, signatureV3Generator);
} else { } else {
@ -415,7 +394,7 @@ public class PgpSignEncrypt {
if (enableSignature) { if (enableSignature) {
updateProgress(R.string.progress_generating_signature, 95, 100); updateProgress(R.string.progress_generating_signature, 95, 100);
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator.generate().encode(pOut); signatureV3Generator.generate().encode(pOut);
} else { } else {
signatureGenerator.generate().encode(pOut); signatureGenerator.generate().encode(pOut);
@ -432,12 +411,12 @@ public class PgpSignEncrypt {
encryptionOut.close(); encryptionOut.close();
} }
if (enableAsciiArmorOutput) { if (mEnableAsciiArmorOutput) {
armorOut.close(); armorOut.close();
} }
out.close(); out.close();
outStream.close(); mOutStream.close();
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
} }
@ -449,35 +428,36 @@ public class PgpSignEncrypt {
SignatureException { SignatureException {
OutputStream out; OutputStream out;
if (enableAsciiArmorOutput) { if (mEnableAsciiArmorOutput) {
// Ascii Armor (Radix-64) // Ascii Armor (Radix-64)
ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(context)); armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
out = armorOut; out = armorOut;
} else { } else {
out = outStream; out = mOutStream;
} }
if (signatureKeyId == 0) { if (mSignatureKeyId == 0) {
throw new PgpGeneralException(context.getString(R.string.error_no_signature_key)); throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key));
} }
PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId); PGPSecretKeyRing signingKeyRing =
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId); ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
if (signingKey == null) { if (signingKey == null) {
throw new PgpGeneralException(context.getString(R.string.error_signature_failed)); throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
} }
if (signaturePassphrase == null) { if (mSignaturePassphrase == null) {
throw new PgpGeneralException(context.getString(R.string.error_no_signature_passphrase)); throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase));
} }
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(signaturePassphrase.toCharArray()); Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mSignaturePassphrase.toCharArray());
PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor); PGPPrivateKey signaturePrivateKey = signingKey.extractPrivateKey(keyDecryptor);
if (signaturePrivateKey == null) { if (signaturePrivateKey == null) {
throw new PgpGeneralException( throw new PgpGeneralException(
context.getString(R.string.error_could_not_extract_private_key)); mContext.getString(R.string.error_could_not_extract_private_key));
} }
updateProgress(R.string.progress_preparing_streams, 0, 100); updateProgress(R.string.progress_preparing_streams, 0, 100);
@ -490,12 +470,12 @@ public class PgpSignEncrypt {
// content signer based on signing key algorithm and chosen hash algorithm // content signer based on signing key algorithm and chosen hash algorithm
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
.getPublicKey().getAlgorithm(), signatureHashAlgorithm) .getPublicKey().getAlgorithm(), mSignatureHashAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator signatureGenerator = null; PGPSignatureGenerator signatureGenerator = null;
PGPV3SignatureGenerator signatureV3Generator = null; PGPV3SignatureGenerator signatureV3Generator = null;
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder); signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
signatureV3Generator.init(type, signaturePrivateKey); signatureV3Generator.init(type, signaturePrivateKey);
} else { } else {
@ -510,7 +490,7 @@ public class PgpSignEncrypt {
updateProgress(R.string.progress_signing, 40, 100); updateProgress(R.string.progress_signing, 40, 100);
InputStream inStream = data.getInputStream(); InputStream inStream = mData.getInputStream();
// if (binary) { // if (binary) {
// byte[] buffer = new byte[1 << 16]; // byte[] buffer = new byte[1 << 16];
// int n = 0; // int n = 0;
@ -527,7 +507,7 @@ public class PgpSignEncrypt {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (signatureForceV3) { if (mSignatureForceV3) {
processLine(line, null, signatureV3Generator); processLine(line, null, signatureV3Generator);
signatureV3Generator.update(newline); signatureV3Generator.update(newline);
} else { } else {
@ -538,13 +518,13 @@ public class PgpSignEncrypt {
// } // }
BCPGOutputStream bOut = new BCPGOutputStream(out); BCPGOutputStream bOut = new BCPGOutputStream(out);
if (signatureForceV3) { if (mSignatureForceV3) {
signatureV3Generator.generate().encode(bOut); signatureV3Generator.generate().encode(bOut);
} else { } else {
signatureGenerator.generate().encode(bOut); signatureGenerator.generate().encode(bOut);
} }
out.close(); out.close();
outStream.close(); mOutStream.close();
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
} }

View File

@ -1,33 +1,24 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.spongycastle.asn1.DERObjectIdentifier; import org.spongycastle.asn1.DERObjectIdentifier;
import org.spongycastle.asn1.x509.AuthorityKeyIdentifier; import org.spongycastle.asn1.x509.*;
import org.spongycastle.asn1.x509.BasicConstraints;
import org.spongycastle.asn1.x509.GeneralName;
import org.spongycastle.asn1.x509.GeneralNames;
import org.spongycastle.asn1.x509.SubjectKeyIdentifier;
import org.spongycastle.asn1.x509.X509Extensions;
import org.spongycastle.asn1.x509.X509Name;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
@ -38,9 +29,23 @@ import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;
public class PgpToX509 { public class PgpToX509 {
public final static String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge"; public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
public final static String DN_COMMON_PART_OU = "OpenPGP Keychain cert"; public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert";
/** /**
* Creates a self-signed certificate from a public and private key. The (critical) key-usage * Creates a self-signed certificate from a public and private key. The (critical) key-usage
@ -48,20 +53,14 @@ public class PgpToX509 {
* and certificate-signing. The (non-critical) Netscape extension is set up with: SSL client and * and certificate-signing. The (non-critical) Netscape extension is set up with: SSL client and
* S/MIME. A URI subjectAltName may also be set up. * S/MIME. A URI subjectAltName may also be set up.
* *
* @param pubKey * @param pubKey public key
* public key * @param privKey private key
* @param privKey * @param subject subject (and issuer) DN for this certificate, RFC 2253 format preferred.
* private key * @param startDate date from which the certificate will be valid (defaults to current date and time
* @param subject * if null)
* subject (and issuer) DN for this certificate, RFC 2253 format preferred. * @param endDate date until which the certificate will be valid (defaults to current date and time
* @param startDate * if null) *
* date from which the certificate will be valid (defaults to current date and time * @param subjAltNameURI URI to be placed in subjectAltName
* if null)
* @param endDate
* date until which the certificate will be valid (defaults to current date and time
* if null) *
* @param subjAltNameURI
* URI to be placed in subjectAltName
* @return self-signed certificate * @return self-signed certificate
* @throws InvalidKeyException * @throws InvalidKeyException
* @throws SignatureException * @throws SignatureException
@ -70,11 +69,10 @@ public class PgpToX509 {
* @throws NoSuchProviderException * @throws NoSuchProviderException
* @throws CertificateException * @throws CertificateException
* @throws Exception * @throws Exception
*
* @author Bruno Harbulot * @author Bruno Harbulot
*/ */
public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey, public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey,
X509Name subject, Date startDate, Date endDate, String subjAltNameURI) X509Name subject, Date startDate, Date endDate, String subjAltNameURI)
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
SignatureException, CertificateException, NoSuchProviderException { SignatureException, CertificateException, NoSuchProviderException {
@ -172,14 +170,11 @@ public class PgpToX509 {
/** /**
* Creates a self-signed certificate from a PGP Secret Key. * Creates a self-signed certificate from a PGP Secret Key.
* *
* @param pgpSecKey * @param pgpSecKey PGP Secret Key (from which one can extract the public and private keys and other
* PGP Secret Key (from which one can extract the public and private keys and other * attributes).
* attributes). * @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks should be done
* @param pgpPrivKey * before calling this method)
* PGP Private Key corresponding to the Secret Key (password callbacks should be done * @param subjAltNameURI optional URI to embed in the subject alternative-name
* before calling this method)
* @param subjAltNameURI
* optional URI to embed in the subject alternative-name
* @return self-signed certificate * @return self-signed certificate
* @throws PGPException * @throws PGPException
* @throws NoSuchProviderException * @throws NoSuchProviderException
@ -187,11 +182,10 @@ public class PgpToX509 {
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
* @throws SignatureException * @throws SignatureException
* @throws CertificateException * @throws CertificateException
*
* @author Bruno Harbulot * @author Bruno Harbulot
*/ */
public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey, public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey,
PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException, PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException,
NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
SignatureException, CertificateException { SignatureException, CertificateException {
// get public key from secret key // get public key from secret key
@ -213,7 +207,7 @@ public class PgpToX509 {
x509NameValues.add(DN_COMMON_PART_OU); x509NameValues.add(DN_COMMON_PART_OU);
for (@SuppressWarnings("unchecked") for (@SuppressWarnings("unchecked")
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext();) { Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext(); ) {
Object attrib = it.next(); Object attrib = it.next();
x509NameOids.add(X509Name.CN); x509NameOids.add(X509Name.CN);
x509NameValues.add("CryptoCall"); x509NameValues.add("CryptoCall");
@ -225,7 +219,7 @@ public class PgpToX509 {
*/ */
Log.d(Constants.TAG, "User attributes: "); Log.d(Constants.TAG, "User attributes: ");
for (@SuppressWarnings("unchecked") for (@SuppressWarnings("unchecked")
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext();) { Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext(); ) {
Object attrib = it.next(); Object attrib = it.next();
Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass()); Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass());
} }
@ -263,12 +257,11 @@ public class PgpToX509 {
* allow passwords to be stored within your application. * allow passwords to be stored within your application.
* *
* @author Bruno Harbulot. * @author Bruno Harbulot.
*
*/ */
public final static class PredefinedPasswordCallbackHandler implements CallbackHandler { public static final class PredefinedPasswordCallbackHandler implements CallbackHandler {
private char[] password; private char[] mPassword;
private String prompt; private String mPrompt;
public PredefinedPasswordCallbackHandler(String password) { public PredefinedPasswordCallbackHandler(String password) {
this(password == null ? null : password.toCharArray(), null); this(password == null ? null : password.toCharArray(), null);
@ -283,16 +276,16 @@ public class PgpToX509 {
} }
public PredefinedPasswordCallbackHandler(char[] password, String prompt) { public PredefinedPasswordCallbackHandler(char[] password, String prompt) {
this.password = password; this.mPassword = password;
this.prompt = prompt; this.mPrompt = prompt;
} }
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) { for (Callback callback : callbacks) {
if (callback instanceof PasswordCallback) { if (callback instanceof PasswordCallback) {
PasswordCallback pwCallback = (PasswordCallback) callback; PasswordCallback pwCallback = (PasswordCallback) callback;
if ((this.prompt == null) || (this.prompt.equals(pwCallback.getPrompt()))) { if ((this.mPrompt == null) || (this.mPrompt.equals(pwCallback.getPrompt()))) {
pwCallback.setPassword(this.password); pwCallback.setPassword(this.mPassword);
} }
} else { } else {
throw new UnsupportedCallbackException(callback, "Unrecognised callback."); throw new UnsupportedCallbackException(callback, "Unrecognised callback.");

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.pgp.exception; package org.sufficientlysecure.keychain.pgp.exception;
public class NoAsymmetricEncryptionException extends Exception { public class NoAsymmetricEncryptionException extends Exception {

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.pgp.exception; package org.sufficientlysecure.keychain.pgp.exception;
public class PgpGeneralException extends Exception { public class PgpGeneralException extends Exception {

View File

@ -17,11 +17,11 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
import android.net.Uri; import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
public class KeychainContract { public class KeychainContract {
interface KeyRingsColumns { interface KeyRingsColumns {
@ -57,10 +57,15 @@ public class KeychainContract {
interface ApiAppsColumns { interface ApiAppsColumns {
String PACKAGE_NAME = "package_name"; String PACKAGE_NAME = "package_name";
String PACKAGE_SIGNATURE = "package_signature"; String PACKAGE_SIGNATURE = "package_signature";
}
interface ApiAppsAccountsColumns {
String ACCOUNT_NAME = "account_name";
String KEY_ID = "key_id"; // not a database id String KEY_ID = "key_id"; // not a database id
String ENCRYPTION_ALGORITHM = "encryption_algorithm"; String ENCRYPTION_ALGORITHM = "encryption_algorithm";
String HASH_ALORITHM = "hash_algorithm"; String HASH_ALORITHM = "hash_algorithm";
String COMPRESSION = "compression"; String COMPRESSION = "compression";
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
} }
public static final class KeyTypes { public static final class KeyTypes {
@ -88,18 +93,26 @@ public class KeychainContract {
public static final String PATH_KEYS = "keys"; public static final String PATH_KEYS = "keys";
public static final String BASE_API_APPS = "api_apps"; public static final String BASE_API_APPS = "api_apps";
public static final String PATH_BY_PACKAGE_NAME = "package_name"; public static final String PATH_ACCOUNTS = "accounts";
public static class KeyRings implements KeyRingsColumns, BaseColumns { public static class KeyRings implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
/** Use if multiple items get returned */ /**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key_ring"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key_ring";
/** Use if a single item is returned */ /**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring";
public static Uri buildUnifiedKeyRingsUri() {
return CONTENT_URI;
}
public static Uri buildPublicKeyRingsUri() { public static Uri buildPublicKeyRingsUri() {
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build(); return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
} }
@ -147,6 +160,7 @@ public class KeychainContract {
} }
public static Uri buildSecretKeyRingsByEmailsUri(String emails) { public static Uri buildSecretKeyRingsByEmailsUri(String emails) {
// TODO: encoded?
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS) return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS)
.appendPath(emails).build(); .appendPath(emails).build();
} }
@ -161,10 +175,14 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
/** Use if multiple items get returned */ /**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key";
/** Use if a single item is returned */ /**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key";
public static Uri buildPublicKeysUri(String keyRingRowId) { public static Uri buildPublicKeysUri(String keyRingRowId) {
@ -200,10 +218,14 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build(); .appendPath(BASE_KEY_RINGS).build();
/** Use if multiple items get returned */ /**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id";
/** Use if a single item is returned */ /**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
public static Uri buildPublicUserIdsUri(String keyRingRowId) { public static Uri buildPublicUserIdsUri(String keyRingRowId) {
@ -239,20 +261,44 @@ public class KeychainContract {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build(); .appendPath(BASE_API_APPS).build();
/** Use if multiple items get returned */ /**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps";
/** Use if a single item is returned */ /**
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps"; * Use if a single item is returned
*/
public static Uri buildIdUri(String rowId) { public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_app";
return CONTENT_URI.buildUpon().appendPath(rowId).build();
}
public static Uri buildByPackageNameUri(String packageName) { public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName) return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build();
}
}
public static class ApiAccounts implements ApiAppsAccountsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_app.accounts";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_app.account";
public static Uri buildBaseUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
.build(); .build();
} }
public static Uri buildByPackageAndAccountUri(String packageName, String accountName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
.appendEncodedPath(accountName).build();
}
} }
public static class DataStream { public static class DataStream {

View File

@ -17,27 +17,29 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
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.util.Log;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
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.util.Log;
public class KeychainDatabase extends SQLiteOpenHelper { public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "apg.db"; private static final String DATABASE_NAME = "apg.db";
private static final int DATABASE_VERSION = 7; private static final int DATABASE_VERSION = 8;
public interface Tables { public interface Tables {
String KEY_RINGS = "key_rings"; String KEY_RINGS = "key_rings";
String KEYS = "keys"; String KEYS = "keys";
String USER_IDS = "user_ids"; String USER_IDS = "user_ids";
String API_APPS = "api_apps"; String API_APPS = "api_apps";
String API_ACCOUNTS = "api_accounts";
} }
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
@ -62,26 +64,35 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ KeysColumns.KEY_DATA + " BLOB," + KeysColumns.KEY_DATA + " BLOB,"
+ KeysColumns.RANK + " INTEGER, " + KeysColumns.RANK + " INTEGER, "
+ KeysColumns.FINGERPRINT + " BLOB, " + KeysColumns.FINGERPRINT + " BLOB, "
+ KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY(" + KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, "
+ KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "(" + "FOREIGN KEY(" + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES "
+ BaseColumns._ID + ") ON DELETE CASCADE)"; + Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ UserIdsColumns.USER_ID + " TEXT, " + UserIdsColumns.USER_ID + " TEXT, "
+ UserIdsColumns.RANK + " INTEGER, " + UserIdsColumns.RANK + " INTEGER, "
+ UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY(" + UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, "
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "(" + "FOREIGN KEY(" + UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES "
+ BaseColumns._ID + ") ON DELETE CASCADE)"; + Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, " + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB, " + ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB)";
+ ApiAppsColumns.KEY_ID + " INT64, "
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, " private static final String CREATE_API_APPS_ACCOUNTS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ACCOUNTS
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, " + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.COMPRESSION + " INTEGER)"; + ApiAppsAccountsColumns.ACCOUNT_NAME + " TEXT NOT NULL, "
+ ApiAppsAccountsColumns.KEY_ID + " INT64, "
+ ApiAppsAccountsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
+ ApiAppsAccountsColumns.HASH_ALORITHM + " INTEGER, "
+ ApiAppsAccountsColumns.COMPRESSION + " INTEGER, "
+ ApiAppsAccountsColumns.PACKAGE_NAME + " TEXT NOT NULL, "
+ "UNIQUE(" + ApiAppsAccountsColumns.ACCOUNT_NAME + ", "
+ ApiAppsAccountsColumns.PACKAGE_NAME + "), "
+ "FOREIGN KEY(" + ApiAppsAccountsColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE)";
KeychainDatabase(Context context) { KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -95,6 +106,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYS); db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS); db.execSQL(CREATE_USER_IDS);
db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
} }
@Override @Override
@ -134,6 +146,12 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT
+ " BLOB;"); + " BLOB;");
break; break;
case 7:
// new db layout for api apps
db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
break;
default: default:
break; break;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org> * Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,21 +17,6 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import java.util.Arrays;
import java.util.HashMap;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.Log;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.UriMatcher; import android.content.UriMatcher;
@ -44,6 +29,22 @@ import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import android.text.TextUtils; import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Arrays;
import java.util.HashMap;
public class KeychainProvider extends ContentProvider { public class KeychainProvider extends ContentProvider {
// public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME // public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME
// + ".action.DATABASE_CHANGE"; // + ".action.DATABASE_CHANGE";
@ -63,6 +64,7 @@ public class KeychainProvider extends ContentProvider {
private static final int PUBLIC_KEY_RING_USER_ID = 121; private static final int PUBLIC_KEY_RING_USER_ID = 121;
private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122; private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122;
private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID = 123;
private static final int SECRET_KEY_RING = 201; private static final int SECRET_KEY_RING = 201;
private static final int SECRET_KEY_RING_BY_ROW_ID = 202; private static final int SECRET_KEY_RING_BY_ROW_ID = 202;
@ -78,8 +80,11 @@ public class KeychainProvider extends ContentProvider {
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222; private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
private static final int API_APPS = 301; private static final int API_APPS = 301;
private static final int API_APPS_BY_ROW_ID = 302;
private static final int API_APPS_BY_PACKAGE_NAME = 303; private static final int API_APPS_BY_PACKAGE_NAME = 303;
private static final int API_ACCOUNTS = 304;
private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306;
private static final int UNIFIED_KEY_RING = 401;
// private static final int DATA_STREAM = 401; // private static final int DATA_STREAM = 401;
@ -94,6 +99,15 @@ public class KeychainProvider extends ContentProvider {
String authority = KeychainContract.CONTENT_AUTHORITY; String authority = KeychainContract.CONTENT_AUTHORITY;
/**
* unified key rings
*
* <pre>
* key_rings
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS, UNIFIED_KEY_RING);
/** /**
* public key rings * public key rings
* *
@ -147,6 +161,7 @@ public class KeychainProvider extends ContentProvider {
* <pre> * <pre>
* key_rings/public/#/user_ids * key_rings/public/#/user_ids
* key_rings/public/#/user_ids/# * key_rings/public/#/user_ids/#
* key_rings/public/master_key_id/#/user_ids
* </pre> * </pre>
*/ */
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
@ -155,6 +170,10 @@ public class KeychainProvider extends ContentProvider {
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#", + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
PUBLIC_KEY_RING_USER_ID_BY_ROW_ID); PUBLIC_KEY_RING_USER_ID_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/"
+ KeychainContract.PATH_BY_MASTER_KEY_ID + "/*/" + KeychainContract.PATH_USER_IDS,
PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID);
/** /**
* secret key rings * secret key rings
@ -220,11 +239,22 @@ public class KeychainProvider extends ContentProvider {
/** /**
* API apps * API apps
*
* <pre>
* api_apps
* api_apps/_ (package name)
*
* api_apps/_/accounts
* api_apps/_/accounts/_ (account name)
* </pre>
*/ */
matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ KeychainContract.PATH_ACCOUNTS, API_ACCOUNTS);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ KeychainContract.PATH_ACCOUNTS + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME);
/** /**
* data stream * data stream
@ -238,7 +268,7 @@ public class KeychainProvider extends ContentProvider {
return matcher; return matcher;
} }
private KeychainDatabase mApgDatabase; private KeychainDatabase mKeychainDatabase;
/** /**
* {@inheritDoc} * {@inheritDoc}
@ -246,7 +276,7 @@ public class KeychainProvider extends ContentProvider {
@Override @Override
public boolean onCreate() { public boolean onCreate() {
mUriMatcher = buildUriMatcher(); mUriMatcher = buildUriMatcher();
mApgDatabase = new KeychainDatabase(getContext()); mKeychainDatabase = new KeychainDatabase(getContext());
return true; return true;
} }
@ -282,6 +312,7 @@ public class KeychainProvider extends ContentProvider {
return Keys.CONTENT_ITEM_TYPE; return Keys.CONTENT_ITEM_TYPE;
case PUBLIC_KEY_RING_USER_ID: case PUBLIC_KEY_RING_USER_ID:
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
case SECRET_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID:
return UserIds.CONTENT_TYPE; return UserIds.CONTENT_TYPE;
@ -292,10 +323,15 @@ public class KeychainProvider extends ContentProvider {
case API_APPS: case API_APPS:
return ApiApps.CONTENT_TYPE; return ApiApps.CONTENT_TYPE;
case API_APPS_BY_ROW_ID:
case API_APPS_BY_PACKAGE_NAME: case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE; return ApiApps.CONTENT_ITEM_TYPE;
case API_ACCOUNTS:
return ApiAccounts.CONTENT_TYPE;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
return ApiAccounts.CONTENT_ITEM_TYPE;
default: default:
throw new UnsupportedOperationException("Unknown uri: " + uri); throw new UnsupportedOperationException("Unknown uri: " + uri);
} }
@ -304,7 +340,7 @@ public class KeychainProvider extends ContentProvider {
/** /**
* Returns type of the query (secret/public) * Returns type of the query (secret/public)
* *
* @param uri * @param match
* @return * @return
*/ */
private int getKeyType(int match) { private int getKeyType(int match) {
@ -319,6 +355,7 @@ public class KeychainProvider extends ContentProvider {
case PUBLIC_KEY_RING_KEY: case PUBLIC_KEY_RING_KEY:
case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case PUBLIC_KEY_RING_USER_ID: case PUBLIC_KEY_RING_USER_ID:
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
type = KeyTypes.PUBLIC; type = KeyTypes.PUBLIC;
break; break;
@ -356,15 +393,25 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID); projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID);
projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "." projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "."
+ KeyRingsColumns.KEY_RING_DATA); + KeyRingsColumns.KEY_RING_DATA);
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID); projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "."
+ KeyRingsColumns.MASTER_KEY_ID);
// TODO: deprecated master key id // TODO: deprecated master key id
//projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID); //projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID);
projectionMap.put(KeysColumns.ALGORITHM, Tables.KEYS + "." + KeysColumns.ALGORITHM);
projectionMap.put(KeysColumns.KEY_SIZE, Tables.KEYS + "." + KeysColumns.KEY_SIZE);
projectionMap.put(KeysColumns.CREATION, Tables.KEYS + "." + KeysColumns.CREATION);
projectionMap.put(KeysColumns.EXPIRY, Tables.KEYS + "." + KeysColumns.EXPIRY);
projectionMap.put(KeysColumns.KEY_RING_ROW_ID, Tables.KEYS + "." + KeysColumns.KEY_RING_ROW_ID);
projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT); projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT);
projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED); projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED);
projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
// type attribute is special: if there is any grouping, choose secret over public type
projectionMap.put(KeyRingsColumns.TYPE,
"MAX(" + Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + ") AS " + KeyRingsColumns.TYPE);
return projectionMap; return projectionMap;
} }
@ -395,13 +442,27 @@ public class KeychainProvider extends ContentProvider {
return projectionMap; return projectionMap;
} }
private HashMap<String, String> getProjectionMapForUserIds() {
HashMap<String, String> projectionMap = new HashMap<String, String>();
projectionMap.put(BaseColumns._ID, Tables.USER_IDS + "." + BaseColumns._ID);
projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID);
projectionMap.put(UserIdsColumns.RANK, Tables.USER_IDS + "." + UserIdsColumns.RANK);
projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "."
+ KeyRingsColumns.MASTER_KEY_ID);
return projectionMap;
}
/** /**
* Builds default query for keyRings: KeyRings table is joined with UserIds and Keys * Builds default query for keyRings: KeyRings table is joined with UserIds and Keys
*/ */
private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match) { private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match) {
// public or secret keyring if (match != UNIFIED_KEY_RING) {
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); // public or secret keyring
qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
}
// join keyrings with keys and userIds // join keyrings with keys and userIds
// Only get user id and key with rank 0 (main user id and main key) // Only get user id and key with rank 0 (main user id and main key)
@ -451,11 +512,26 @@ public class KeychainProvider extends ContentProvider {
Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase db = mApgDatabase.getReadableDatabase(); SQLiteDatabase db = mKeychainDatabase.getReadableDatabase();
int match = mUriMatcher.match(uri); int match = mUriMatcher.match(uri);
// all query() parameters, for good measure
String groupBy = null, having = null;
switch (match) { switch (match) {
case UNIFIED_KEY_RING:
qb = buildKeyRingQuery(qb, match);
// GROUP BY so we don't get duplicates
groupBy = Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID;
if (TextUtils.isEmpty(sortOrder)) {
sortOrder = KeyRings.TYPE + " DESC, " +
Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC";
}
break;
case PUBLIC_KEY_RING: case PUBLIC_KEY_RING:
case SECRET_KEY_RING: case SECRET_KEY_RING:
qb = buildKeyRingQuery(qb, match); qb = buildKeyRingQuery(qb, match);
@ -465,7 +541,6 @@ public class KeychainProvider extends ContentProvider {
} }
break; break;
case PUBLIC_KEY_RING_BY_ROW_ID: case PUBLIC_KEY_RING_BY_ROW_ID:
case SECRET_KEY_RING_BY_ROW_ID: case SECRET_KEY_RING_BY_ROW_ID:
qb = buildKeyRingQuery(qb, match); qb = buildKeyRingQuery(qb, match);
@ -478,7 +553,6 @@ public class KeychainProvider extends ContentProvider {
} }
break; break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
case SECRET_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID:
qb = buildKeyRingQuery(qb, match); qb = buildKeyRingQuery(qb, match);
@ -491,7 +565,6 @@ public class KeychainProvider extends ContentProvider {
} }
break; break;
case SECRET_KEY_RING_BY_KEY_ID: case SECRET_KEY_RING_BY_KEY_ID:
case PUBLIC_KEY_RING_BY_KEY_ID: case PUBLIC_KEY_RING_BY_KEY_ID:
qb = buildKeyRingQueryWithSpecificKey(qb, match); qb = buildKeyRingQueryWithSpecificKey(qb, match);
@ -504,7 +577,6 @@ public class KeychainProvider extends ContentProvider {
} }
break; break;
case SECRET_KEY_RING_BY_EMAILS: case SECRET_KEY_RING_BY_EMAILS:
case PUBLIC_KEY_RING_BY_EMAILS: case PUBLIC_KEY_RING_BY_EMAILS:
qb = buildKeyRingQuery(qb, match); qb = buildKeyRingQuery(qb, match);
@ -534,7 +606,6 @@ public class KeychainProvider extends ContentProvider {
} }
break; break;
case SECRET_KEY_RING_BY_LIKE_EMAIL: case SECRET_KEY_RING_BY_LIKE_EMAIL:
case PUBLIC_KEY_RING_BY_LIKE_EMAIL: case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
qb = buildKeyRingQuery(qb, match); qb = buildKeyRingQuery(qb, match);
@ -550,7 +621,6 @@ public class KeychainProvider extends ContentProvider {
+ "))"); + "))");
break; break;
case PUBLIC_KEY_RING_KEY: case PUBLIC_KEY_RING_KEY:
case SECRET_KEY_RING_KEY: case SECRET_KEY_RING_KEY:
qb.setTables(Tables.KEYS); qb.setTables(Tables.KEYS);
@ -563,7 +633,6 @@ public class KeychainProvider extends ContentProvider {
qb.setProjectionMap(getProjectionMapForKeys()); qb.setProjectionMap(getProjectionMapForKeys());
break; break;
case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case SECRET_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID:
qb.setTables(Tables.KEYS); qb.setTables(Tables.KEYS);
@ -579,7 +648,16 @@ public class KeychainProvider extends ContentProvider {
qb.setProjectionMap(getProjectionMapForKeys()); qb.setProjectionMap(getProjectionMapForKeys());
break; break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
qb.setTables(Tables.USER_IDS + " INNER JOIN " + Tables.KEY_RINGS + " ON " + "("
+ Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "."
+ KeysColumns.KEY_RING_ROW_ID + " )");
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(3));
qb.setProjectionMap(getProjectionMapForUserIds());
break;
case PUBLIC_KEY_RING_USER_ID: case PUBLIC_KEY_RING_USER_ID:
case SECRET_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID:
qb.setTables(Tables.USER_IDS); qb.setTables(Tables.USER_IDS);
@ -587,7 +665,6 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getPathSegments().get(2)); qb.appendWhereEscapeString(uri.getPathSegments().get(2));
break; break;
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
case SECRET_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
qb.setTables(Tables.USER_IDS); qb.setTables(Tables.USER_IDS);
@ -598,25 +675,31 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment()); qb.appendWhereEscapeString(uri.getLastPathSegment());
break; break;
case API_APPS: case API_APPS:
qb.setTables(Tables.API_APPS); qb.setTables(Tables.API_APPS);
break;
case API_APPS_BY_ROW_ID:
qb.setTables(Tables.API_APPS);
qb.appendWhere(BaseColumns._ID + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break; break;
case API_APPS_BY_PACKAGE_NAME: case API_APPS_BY_PACKAGE_NAME:
qb.setTables(Tables.API_APPS); qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(2)); qb.appendWhereEscapeString(uri.getLastPathSegment());
break; break;
case API_ACCOUNTS:
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + ApiAccounts.ACCOUNT_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
default: default:
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
@ -630,7 +713,7 @@ public class KeychainProvider extends ContentProvider {
orderBy = sortOrder; orderBy = sortOrder;
} }
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy); Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
// Tell the cursor what uri to watch, so it knows when its source data changes // Tell the cursor what uri to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri); c.setNotificationUri(getContext().getContentResolver(), uri);
@ -653,7 +736,7 @@ public class KeychainProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) { public Uri insert(Uri uri, ContentValues values) {
Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")"); Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
Uri rowUri = null; Uri rowUri = null;
long rowId = -1; long rowId = -1;
@ -673,12 +756,14 @@ public class KeychainProvider extends ContentProvider {
values.put(Keys.TYPE, KeyTypes.PUBLIC); values.put(Keys.TYPE, KeyTypes.PUBLIC);
rowId = db.insertOrThrow(Tables.KEYS, null, values); rowId = db.insertOrThrow(Tables.KEYS, null, values);
// TODO: this is wrong:
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId)); rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case PUBLIC_KEY_RING_USER_ID: case PUBLIC_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values); rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
// TODO: this is wrong:
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId)); rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
@ -695,18 +780,33 @@ public class KeychainProvider extends ContentProvider {
values.put(Keys.TYPE, KeyTypes.SECRET); values.put(Keys.TYPE, KeyTypes.SECRET);
rowId = db.insertOrThrow(Tables.KEYS, null, values); rowId = db.insertOrThrow(Tables.KEYS, null, values);
// TODO: this is wrong:
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId)); rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break; break;
case SECRET_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values); rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
// TODO: this is wrong:
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId)); rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
break; break;
case API_APPS: case API_APPS:
rowId = db.insertOrThrow(Tables.API_APPS, null, values); rowId = db.insertOrThrow(Tables.API_APPS, null, values);
rowUri = ApiApps.buildIdUri(Long.toString(rowId)); // rowUri = ApiApps.buildIdUri(Long.toString(rowId));
break;
case API_ACCOUNTS:
// set foreign key automatically based on given uri
// e.g., api_apps/com.example.app/accounts/
String packageName = uri.getPathSegments().get(1);
values.put(ApiAccounts.PACKAGE_NAME, packageName);
Log.d(Constants.TAG, "provider packageName: " + packageName);
rowId = db.insertOrThrow(Tables.API_ACCOUNTS, null, values);
// TODO: this is wrong:
// rowUri = ApiAccounts.buildIdUri(Long.toString(rowId));
break; break;
default: default:
@ -730,7 +830,7 @@ public class KeychainProvider extends ContentProvider {
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.v(Constants.TAG, "delete(uri=" + uri + ")"); Log.v(Constants.TAG, "delete(uri=" + uri + ")");
final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
int count; int count;
final int match = mUriMatcher.match(uri); final int match = mUriMatcher.match(uri);
@ -766,12 +866,12 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection), count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
selectionArgs); selectionArgs);
break; break;
case API_APPS_BY_ROW_ID: case API_APPS_BY_PACKAGE_NAME:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection), count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, selection),
selectionArgs); selectionArgs);
break; break;
case API_APPS_BY_PACKAGE_NAME: case API_ACCOUNTS_BY_ACCOUNT_NAME:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection), count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, selection),
selectionArgs); selectionArgs);
break; break;
default: default:
@ -791,7 +891,7 @@ public class KeychainProvider extends ContentProvider {
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")"); Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
String defaultSelection = null; String defaultSelection = null;
int count = 0; int count = 0;
@ -836,13 +936,13 @@ public class KeychainProvider extends ContentProvider {
count = db.update(Tables.USER_IDS, values, count = db.update(Tables.USER_IDS, values,
buildDefaultUserIdsSelection(uri, selection), selectionArgs); buildDefaultUserIdsSelection(uri, selection), selectionArgs);
break; break;
case API_APPS_BY_ROW_ID:
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, false, selection), selectionArgs);
break;
case API_APPS_BY_PACKAGE_NAME: case API_APPS_BY_PACKAGE_NAME:
count = db.update(Tables.API_APPS, values, count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, true, selection), selectionArgs); buildDefaultApiAppsSelection(uri, selection), selectionArgs);
break;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
count = db.update(Tables.API_ACCOUNTS, values,
buildDefaultApiAccountsSelection(uri, selection), selectionArgs);
break; break;
default: default:
throw new UnsupportedOperationException("Unknown uri: " + uri); throw new UnsupportedOperationException("Unknown uri: " + uri);
@ -862,7 +962,8 @@ public class KeychainProvider extends ContentProvider {
* Build default selection statement for KeyRings. If no extra selection is specified only build * Build default selection statement for KeyRings. If no extra selection is specified only build
* where clause with rowId * where clause with rowId
* *
* @param uri * @param defaultSelection
* @param keyType
* @param selection * @param selection
* @return * @return
*/ */
@ -940,19 +1041,29 @@ public class KeychainProvider extends ContentProvider {
* @param selection * @param selection
* @return * @return
*/ */
private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) { private String buildDefaultApiAppsSelection(Uri uri, String selection) {
String lastPathSegment = uri.getLastPathSegment(); String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
String andSelection = ""; String andSelection = "";
if (!TextUtils.isEmpty(selection)) { if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")"; andSelection = " AND (" + selection + ")";
} }
if (packageSelection) { return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection;
return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection; }
} else {
return BaseColumns._ID + "=" + lastPathSegment + andSelection; private String buildDefaultApiAccountsSelection(Uri uri, String selection) {
String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1));
String accountName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
} }
return ApiAccounts.PACKAGE_NAME + "=" + packageName + " AND "
+ ApiAccounts.ACCOUNT_NAME + "=" + accountName
+ andSelection;
} }
// @Override // @Override

View File

@ -17,10 +17,9 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
import android.net.Uri; import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
public class KeychainServiceBlobContract { public class KeychainServiceBlobContract {

View File

@ -22,7 +22,6 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns; import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
public class KeychainServiceBlobDatabase extends SQLiteOpenHelper { public class KeychainServiceBlobDatabase extends SQLiteOpenHelper {

View File

@ -18,11 +18,6 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.Blobs;
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
import org.sufficientlysecure.keychain.util.Log;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.ContentValues; import android.content.ContentValues;
@ -31,7 +26,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns; import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.Blobs;
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
import org.sufficientlysecure.keychain.util.Log;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -40,7 +38,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
public class KeychainServiceBlobProvider extends ContentProvider { public class KeychainServiceBlobProvider extends ContentProvider {
private static final String STORE_PATH = Constants.path.APP_DIR + "/ApgBlobs"; private static final String STORE_PATH = Constants.Path.APP_DIR + "/KeychainBlobs";
private KeychainServiceBlobDatabase mBlobDatabase = null; private KeychainServiceBlobDatabase mBlobDatabase = null;
@ -55,7 +53,9 @@ public class KeychainServiceBlobProvider extends ContentProvider {
return true; return true;
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
public Uri insert(Uri uri, ContentValues ignored) { public Uri insert(Uri uri, ContentValues ignored) {
// ContentValues are actually ignored, because we want to store a blob with no more // ContentValues are actually ignored, because we want to store a blob with no more
@ -74,7 +74,9 @@ public class KeychainServiceBlobProvider extends ContentProvider {
return Uri.withAppendedPath(insertedUri, password); return Uri.withAppendedPath(insertedUri, password);
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException,
FileNotFoundException { FileNotFoundException {
@ -91,9 +93,9 @@ public class KeychainServiceBlobProvider extends ContentProvider {
// get the data // get the data
SQLiteDatabase db = mBlobDatabase.getReadableDatabase(); SQLiteDatabase db = mBlobDatabase.getReadableDatabase();
Cursor result = db.query(KeychainServiceBlobDatabase.TABLE, new String[] { BaseColumns._ID }, Cursor result = db.query(KeychainServiceBlobDatabase.TABLE, new String[]{BaseColumns._ID},
BaseColumns._ID + " = ? and " + BlobsColumns.KEY + " = ?", BaseColumns._ID + " = ? and " + BlobsColumns.KEY + " = ?",
new String[] { id, key }, null, null, null); new String[]{id, key}, null, null, null);
if (result.getCount() == 0) { if (result.getCount() == 0) {
// either the key is wrong or no id exists // either the key is wrong or no id exists
@ -124,26 +126,34 @@ public class KeychainServiceBlobProvider extends ContentProvider {
return null; return null;
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
public String getType(Uri uri) { public String getType(Uri uri) {
return null; return null;
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) { String sortOrder) {
return null; return null;
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0; return 0;
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0; return 0;

View File

@ -17,10 +17,11 @@
package org.sufficientlysecure.keychain.provider; package org.sufficientlysecure.keychain.provider;
import java.io.ByteArrayOutputStream; import android.content.*;
import java.io.IOException; import android.database.Cursor;
import java.util.ArrayList; import android.database.DatabaseUtils;
import java.util.Date; import android.net.Uri;
import android.os.RemoteException;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
@ -38,19 +39,15 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.service.remote.AppSettings; import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.content.ContentProviderOperation; import java.io.ByteArrayOutputStream;
import android.content.ContentResolver; import java.io.IOException;
import android.content.ContentValues; import java.util.ArrayList;
import android.content.Context; import java.util.Date;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
public class ProviderHelper { public class ProviderHelper {
@ -87,7 +84,7 @@ public class ProviderHelper {
} }
/** /**
* Retrieves the actual PGPPublicKeyRing object from the database blob based on the maserKeyId * Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId
*/ */
public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context, public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context,
long masterKeyId) { long masterKeyId) {
@ -110,11 +107,8 @@ public class ProviderHelper {
*/ */
public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) { public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId); PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId);
if (keyRing == null) {
return null;
}
return keyRing.getPublicKey(keyId); return (keyRing == null) ? null : keyRing.getPublicKey(keyId);
} }
/** /**
@ -149,11 +143,8 @@ public class ProviderHelper {
*/ */
public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) { public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId); PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId);
if (keyRing == null) {
return null;
}
return keyRing.getSecretKey(keyId); return (keyRing == null) ? null : keyRing.getSecretKey(keyId);
} }
/** /**
@ -168,7 +159,8 @@ public class ProviderHelper {
// get current _ID of key // get current _ID of key
long currentRowId = -1; long currentRowId = -1;
Cursor oldQuery = context.getContentResolver().query(deleteUri, new String[]{KeyRings._ID}, null, null, null); Cursor oldQuery = context.getContentResolver()
.query(deleteUri, new String[]{KeyRings._ID}, null, null, null);
if (oldQuery != null && oldQuery.moveToFirst()) { if (oldQuery != null && oldQuery.moveToFirst()) {
currentRowId = oldQuery.getLong(0); currentRowId = oldQuery.getLong(0);
} else { } else {
@ -184,10 +176,12 @@ public class ProviderHelper {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// use exactly the same _ID again to replace key in-place. // use exactly the same _ID again to replace key in-place.
// NOTE: If we would not use the same _ID again, getting back to the ViewKeyActivity would result in Nullpointer, // NOTE: If we would not use the same _ID again,
// getting back to the ViewKeyActivity would result in Nullpointer,
// because the currently loaded key would be gone from the database // because the currently loaded key would be gone from the database
if (currentRowId != -1) if (currentRowId != -1) {
values.put(KeyRings._ID, currentRowId); values.put(KeyRings._ID, currentRowId);
}
values.put(KeyRings.MASTER_KEY_ID, masterKeyId); values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
@ -211,8 +205,11 @@ public class ProviderHelper {
++userIdRank; ++userIdRank;
} }
for (PGPSignature certification : new IterableIterator<PGPSignature>(masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) { for (PGPSignature certification :
//TODO: how to do this?? we need to verify the signatures again and again when they are displayed... new IterableIterator<PGPSignature>(
masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) {
// TODO: how to do this??
// we need to verify the signatures again and again when they are displayed...
// if (certification.verify // if (certification.verify
// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank)); // operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
} }
@ -239,7 +236,8 @@ public class ProviderHelper {
// get current _ID of key // get current _ID of key
long currentRowId = -1; long currentRowId = -1;
Cursor oldQuery = context.getContentResolver().query(deleteUri, new String[]{KeyRings._ID}, null, null, null); Cursor oldQuery = context.getContentResolver()
.query(deleteUri, new String[]{KeyRings._ID}, null, null, null);
if (oldQuery != null && oldQuery.moveToFirst()) { if (oldQuery != null && oldQuery.moveToFirst()) {
currentRowId = oldQuery.getLong(0); currentRowId = oldQuery.getLong(0);
} else { } else {
@ -255,10 +253,12 @@ public class ProviderHelper {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// use exactly the same _ID again to replace key in-place. // use exactly the same _ID again to replace key in-place.
// NOTE: If we would not use the same _ID again, getting back to the ViewKeyActivity would result in Nullpointer, // NOTE: If we would not use the same _ID again,
// getting back to the ViewKeyActivity would result in Nullpointer,
// because the currently loaded key would be gone from the database // because the currently loaded key would be gone from the database
if (currentRowId != -1) if (currentRowId != -1) {
values.put(KeyRings._ID, currentRowId); values.put(KeyRings._ID, currentRowId);
}
values.put(KeyRings.MASTER_KEY_ID, masterKeyId); values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
@ -341,10 +341,10 @@ public class ProviderHelper {
long keyRingRowId, PGPSecretKey key, int rank) throws IOException { long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
boolean has_private = true; boolean hasPrivate = true;
if (key.isMasterKey()) { if (key.isMasterKey()) {
if (PgpKeyHelper.isSecretKeyPrivateEmpty(key)) { if (key.isPrivateKeyEmpty()) {
has_private = false; hasPrivate = false;
} }
} }
@ -352,9 +352,9 @@ public class ProviderHelper {
values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && has_private)); values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && hasPrivate));
values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && has_private)); values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && hasPrivate));
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key) && has_private); values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key) && hasPrivate);
values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
Date expiryDate = PgpKeyHelper.getExpiryDate(key); Date expiryDate = PgpKeyHelper.getExpiryDate(key);
@ -410,6 +410,30 @@ public class ProviderHelper {
return masterKeyIds; return masterKeyIds;
} }
/**
* Private helper method
*/
private static ArrayList<Long> getKeyRingsRowIds(Context context, Uri queryUri) {
Cursor cursor = context.getContentResolver().query(queryUri,
new String[]{KeyRings._ID}, null, null, null);
ArrayList<Long> rowIds = new ArrayList<Long>();
if (cursor != null) {
int idCol = cursor.getColumnIndex(KeyRings._ID);
if (cursor.moveToFirst()) {
do {
rowIds.add(cursor.getLong(idCol));
} while (cursor.moveToNext());
}
}
if (cursor != null) {
cursor.close();
}
return rowIds;
}
/** /**
* Retrieves ids of all SecretKeyRings * Retrieves ids of all SecretKeyRings
*/ */
@ -426,6 +450,22 @@ public class ProviderHelper {
return getKeyRingsMasterKeyIds(context, queryUri); return getKeyRingsMasterKeyIds(context, queryUri);
} }
/**
* Retrieves ids of all SecretKeyRings
*/
public static ArrayList<Long> getSecretKeyRingsRowIds(Context context) {
Uri queryUri = KeyRings.buildSecretKeyRingsUri();
return getKeyRingsRowIds(context, queryUri);
}
/**
* Retrieves ids of all PublicKeyRings
*/
public static ArrayList<Long> getPublicKeyRingsRowIds(Context context) {
Uri queryUri = KeyRings.buildPublicKeyRingsUri();
return getKeyRingsRowIds(context, queryUri);
}
public static void deletePublicKeyRing(Context context, long rowId) { public static void deletePublicKeyRing(Context context, long rowId) {
ContentResolver cr = context.getContentResolver(); ContentResolver cr = context.getContentResolver();
cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null); cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);
@ -436,6 +476,15 @@ public class ProviderHelper {
cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null); cr.delete(KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)), null, null);
} }
public static void deleteUnifiedKeyRing(Context context, String masterKeyId, boolean isSecretKey) {
ContentResolver cr = context.getContentResolver();
cr.delete(KeyRings.buildPublicKeyRingsByMasterKeyIdUri(masterKeyId), null, null);
if (isSecretKey) {
cr.delete(KeyRings.buildSecretKeyRingsByMasterKeyIdUri(masterKeyId), null, null);
}
}
/** /**
* Get master key id of keyring by its row id * Get master key id of keyring by its row id
*/ */
@ -449,13 +498,14 @@ public class ProviderHelper {
*/ */
public static boolean getSecretMasterKeyCanCertify(Context context, long keyRingRowId) { public static boolean getSecretMasterKeyCanCertify(Context context, long keyRingRowId) {
Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId)); Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId));
return getMasterKeyCanCertify(context, queryUri, keyRingRowId); return getMasterKeyCanCertify(context, queryUri);
} }
/** /**
* Private helper method to get master key private empty status of keyring by its row id * Private helper method to get master key private empty status of keyring by its row id
*/ */
private static boolean getMasterKeyCanCertify(Context context, Uri queryUri, long keyRingRowId) {
public static boolean getMasterKeyCanCertify(Context context, Uri queryUri) {
String[] projection = new String[]{ String[] projection = new String[]{
KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID,
"(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
@ -481,6 +531,12 @@ public class ProviderHelper {
return (masterKeyId > 0); return (masterKeyId > 0);
} }
public static boolean hasSecretKeyByMasterKeyId(Context context, long masterKeyId) {
Uri queryUri = KeyRings.buildSecretKeyRingsByMasterKeyIdUri(Long.toString(masterKeyId));
// see if we can get our master key id back from the uri
return getMasterKeyId(context, queryUri) == masterKeyId;
}
/** /**
* Get master key id of keyring by its row id * Get master key id of keyring by its row id
*/ */
@ -512,6 +568,26 @@ public class ProviderHelper {
return masterKeyId; return masterKeyId;
} }
public static long getRowId(Context context, Uri queryUri) {
String[] projection = new String[]{KeyRings._ID};
Cursor cursor = context.getContentResolver().query(queryUri, projection, null, null, null);
long rowId = 0;
try {
if (cursor != null && cursor.moveToFirst()) {
int idCol = cursor.getColumnIndexOrThrow(KeyRings._ID);
rowId = cursor.getLong(idCol);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return rowId;
}
/** /**
* Get fingerprint of key * Get fingerprint of key
*/ */
@ -733,19 +809,28 @@ public class ProviderHelper {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature()); values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
values.put(ApiApps.KEY_ID, appSettings.getKeyId()); return values;
values.put(ApiApps.COMPRESSION, appSettings.getCompression()); }
values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
values.put(ApiApps.HASH_ALORITHM, appSettings.getHashAlgorithm());
private static ContentValues contentValueForApiAccounts(AccountSettings accSettings) {
ContentValues values = new ContentValues();
values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName());
values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId());
values.put(KeychainContract.ApiAccounts.COMPRESSION, accSettings.getCompression());
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, accSettings.getEncryptionAlgorithm());
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, accSettings.getHashAlgorithm());
return values; return values;
} }
public static void insertApiApp(Context context, AppSettings appSettings) { public static void insertApiApp(Context context, AppSettings appSettings) {
context.getContentResolver().insert(ApiApps.CONTENT_URI, context.getContentResolver().insert(KeychainContract.ApiApps.CONTENT_URI,
contentValueForApiApps(appSettings)); contentValueForApiApps(appSettings));
} }
public static void insertApiAccount(Context context, Uri uri, AccountSettings accSettings) {
context.getContentResolver().insert(uri, contentValueForApiAccounts(accSettings));
}
public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) { public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null, if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null,
null) <= 0) { null) <= 0) {
@ -753,30 +838,59 @@ public class ProviderHelper {
} }
} }
public static void updateApiAccount(Context context, AccountSettings accSettings, Uri uri) {
if (context.getContentResolver().update(uri, contentValueForApiAccounts(accSettings), null,
null) <= 0) {
throw new RuntimeException();
}
}
/**
* Must be an uri pointing to an account
*
* @param context
* @param uri
* @return
*/
public static AppSettings getApiAppSettings(Context context, Uri uri) { public static AppSettings getApiAppSettings(Context context, Uri uri) {
AppSettings settings = null; AppSettings settings = null;
Cursor cur = context.getContentResolver().query(uri, null, null, null, null); Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
if (cur != null && cur.moveToFirst()) { if (cur != null && cur.moveToFirst()) {
settings = new AppSettings(); settings = new AppSettings();
settings.setPackageName(cur.getString(cur settings.setPackageName(cur.getString(
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setPackageSignature(cur.getBlob(cur settings.setPackageSignature(cur.getBlob(
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE))); cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID))); }
settings.setCompression(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.ApiApps.COMPRESSION))); return settings;
settings.setHashAlgorithm(cur.getInt(cur }
.getColumnIndexOrThrow(KeychainContract.ApiApps.HASH_ALORITHM)));
settings.setEncryptionAlgorithm(cur.getInt(cur public static AccountSettings getApiAccountSettings(Context context, Uri uri) {
.getColumnIndexOrThrow(KeychainContract.ApiApps.ENCRYPTION_ALGORITHM))); AccountSettings settings = null;
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
if (cur != null && cur.moveToFirst()) {
settings = new AccountSettings();
settings.setAccountName(cur.getString(
cur.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME)));
settings.setKeyId(cur.getLong(
cur.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID)));
settings.setCompression(cur.getInt(
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION)));
settings.setHashAlgorithm(cur.getInt(
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM)));
settings.setEncryptionAlgorithm(cur.getInt(
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM)));
} }
return settings; return settings;
} }
public static byte[] getApiAppSignature(Context context, String packageName) { public static byte[] getApiAppSignature(Context context, String packageName) {
Uri queryUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName); Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE}; String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE};

View File

@ -15,80 +15,71 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData; import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
public class AppSettings { public class AccountSettings {
private String packageName; private String mAccountName;
private byte[] packageSignature; private long mKeyId = Id.key.none;
private long keyId = Id.key.none; private int mEncryptionAlgorithm;
private int encryptionAlgorithm; private int mHashAlgorithm;
private int hashAlgorithm; private int mCompression;
private int compression;
public AppSettings() { public AccountSettings() {
} }
public AppSettings(String packageName, byte[] packageSignature) { public AccountSettings(String accountName) {
super(); super();
this.packageName = packageName; this.mAccountName = accountName;
this.packageSignature = packageSignature;
// defaults: // defaults:
this.encryptionAlgorithm = PGPEncryptedData.AES_256; this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
this.hashAlgorithm = HashAlgorithmTags.SHA512; this.mHashAlgorithm = HashAlgorithmTags.SHA512;
this.compression = Id.choice.compression.zlib; this.mCompression = Id.choice.compression.zlib;
} }
public String getPackageName() { public String getAccountName() {
return packageName; return mAccountName;
} }
public void setPackageName(String packageName) { public void setAccountName(String mAccountName) {
this.packageName = packageName; this.mAccountName = mAccountName;
}
public byte[] getPackageSignature() {
return packageSignature;
}
public void setPackageSignature(byte[] packageSignature) {
this.packageSignature = packageSignature;
} }
public long getKeyId() { public long getKeyId() {
return keyId; return mKeyId;
} }
public void setKeyId(long scretKeyId) { public void setKeyId(long scretKeyId) {
this.keyId = scretKeyId; this.mKeyId = scretKeyId;
} }
public int getEncryptionAlgorithm() { public int getEncryptionAlgorithm() {
return encryptionAlgorithm; return mEncryptionAlgorithm;
} }
public void setEncryptionAlgorithm(int encryptionAlgorithm) { public void setEncryptionAlgorithm(int encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm; this.mEncryptionAlgorithm = encryptionAlgorithm;
} }
public int getHashAlgorithm() { public int getHashAlgorithm() {
return hashAlgorithm; return mHashAlgorithm;
} }
public void setHashAlgorithm(int hashAlgorithm) { public void setHashAlgorithm(int hashAlgorithm) {
this.hashAlgorithm = hashAlgorithm; this.mHashAlgorithm = hashAlgorithm;
} }
public int getCompression() { public int getCompression() {
return compression; return mCompression;
} }
public void setCompression(int compression) { public void setCompression(int compression) {
this.compression = compression; this.mCompression = compression;
} }
} }

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Id;
public class AppSettings {
private String mPackageName;
private byte[] mPackageSignature;
public AppSettings() {
}
public AppSettings(String packageName, byte[] packageSignature) {
super();
this.mPackageName = packageName;
this.mPackageSignature = packageSignature;
}
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String packageName) {
this.mPackageName = packageName;
}
public byte[] getPackageSignature() {
return mPackageSignature;
}
public void setPackageSignature(byte[] packageSignature) {
this.mPackageSignature = packageSignature;
}
}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -47,10 +48,6 @@ import java.util.ArrayList;
public class OpenPgpService extends RemoteService { public class OpenPgpService extends RemoteService {
private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551;
private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552;
private static final int PRIVATE_REQUEST_CODE_GET_KEYS = 553;
/** /**
* Search database for key ids based on emails. * Search database for key ids based on emails.
* *
@ -100,7 +97,9 @@ public class OpenPgpService extends RemoteService {
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds); intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
// return PendingIntent to be executed by client // return PendingIntent to be executed by client
Intent result = new Intent(); Intent result = new Intent();
@ -126,7 +125,9 @@ public class OpenPgpService extends RemoteService {
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId); intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
// pass params through to activity that it can be returned again later to repeat pgp operation // pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
// return PendingIntent to be executed by client // return PendingIntent to be executed by client
Intent result = new Intent(); Intent result = new Intent();
@ -136,7 +137,7 @@ public class OpenPgpService extends RemoteService {
} }
private Intent signImpl(Intent data, ParcelFileDescriptor input, private Intent signImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) { ParcelFileDescriptor output, AccountSettings accSettings) {
try { try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@ -145,11 +146,11 @@ public class OpenPgpService extends RemoteService {
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) { if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else { } else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId()); passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId());
} }
if (passphrase == null) { if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client // get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
return passphraseBundle; return passphraseBundle;
} }
@ -163,9 +164,9 @@ public class OpenPgpService extends RemoteService {
// sign-only // sign-only
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os); PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor) builder.enableAsciiArmorOutput(asciiArmor)
.signatureHashAlgorithm(appSettings.getHashAlgorithm()) .signatureHashAlgorithm(accSettings.getHashAlgorithm())
.signatureForceV3(false) .signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId()) .signatureKeyId(accSettings.getKeyId())
.signaturePassphrase(passphrase); .signaturePassphrase(passphrase);
builder.build().execute(); builder.build().execute();
} finally { } finally {
@ -186,7 +187,7 @@ public class OpenPgpService extends RemoteService {
} }
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input, private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings, boolean sign) { ParcelFileDescriptor output, AccountSettings accSettings, boolean sign) {
try { try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@ -208,14 +209,15 @@ public class OpenPgpService extends RemoteService {
} else { } else {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR, result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!")); new OpenPgpError(OpenPgpError.GENERIC_ERROR,
"Missing parameter user_ids or key_ids!"));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} }
// add own key for encryption // add own key for encryption
keyIds = Arrays.copyOf(keyIds, keyIds.length + 1); keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
keyIds[keyIds.length - 1] = appSettings.getKeyId(); keyIds[keyIds.length - 1] = accSettings.getKeyId();
// build InputData and write into OutputStream // build InputData and write into OutputStream
// Get Input- and OutputStream from ParcelFileDescriptor // Get Input- and OutputStream from ParcelFileDescriptor
@ -227,8 +229,8 @@ public class OpenPgpService extends RemoteService {
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os); PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor) builder.enableAsciiArmorOutput(asciiArmor)
.compressionId(appSettings.getCompression()) .compressionId(accSettings.getCompression())
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm()) .symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
.encryptionKeyIds(keyIds); .encryptionKeyIds(keyIds);
if (sign) { if (sign) {
@ -237,18 +239,18 @@ public class OpenPgpService extends RemoteService {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else { } else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
appSettings.getKeyId()); accSettings.getKeyId());
} }
if (passphrase == null) { if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client // get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
return passphraseBundle; return passphraseBundle;
} }
// sign and encrypt // sign and encrypt
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm()) builder.signatureHashAlgorithm(accSettings.getHashAlgorithm())
.signatureForceV3(false) .signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId()) .signatureKeyId(accSettings.getKeyId())
.signaturePassphrase(passphrase); .signaturePassphrase(passphrase);
} else { } else {
// encrypt only // encrypt only
@ -274,7 +276,7 @@ public class OpenPgpService extends RemoteService {
} }
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input, private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) { ParcelFileDescriptor output, AccountSettings accSettings) {
try { try {
// Get Input- and OutputStream from ParcelFileDescriptor // Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input); InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
@ -289,7 +291,8 @@ public class OpenPgpService extends RemoteService {
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os); PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
builder.assumeSymmetric(false) // no support for symmetric encryption builder.assumeSymmetric(false) // no support for symmetric encryption
.enforcedKeyId(appSettings.getKeyId()) // allow only the private key for this app for decryption // allow only the private key for this app for decryption
.enforcedKeyId(accSettings.getKeyId())
.passphrase(passphrase); .passphrase(passphrase);
// TODO: currently does not support binary signed-only content // TODO: currently does not support binary signed-only content
@ -297,7 +300,7 @@ public class OpenPgpService extends RemoteService {
if (decryptVerifyResult.isKeyPassphraseNeeded()) { if (decryptVerifyResult.isKeyPassphraseNeeded()) {
// get PendingIntent for passphrase input, add it to given params and return to client // get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId()); Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
return passphraseBundle; return passphraseBundle;
} else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) { } else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) {
throw new PgpGeneralException("Decryption of symmetric content not supported by API!"); throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
@ -314,8 +317,9 @@ public class OpenPgpService extends RemoteService {
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo"); intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0); intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
} }
@ -354,8 +358,9 @@ public class OpenPgpService extends RemoteService {
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo"); intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0); intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
@ -403,7 +408,8 @@ public class OpenPgpService extends RemoteService {
// version code is required and needs to correspond to version code of service! // version code is required and needs to correspond to version code of service!
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) { if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
Intent result = new Intent(); Intent result = new Intent();
OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); OpenPgpError error = new OpenPgpError
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
result.putExtra(OpenPgpApi.RESULT_ERROR, error); result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
@ -428,17 +434,26 @@ public class OpenPgpService extends RemoteService {
return errorResult; return errorResult;
} }
final AppSettings appSettings = getAppSettings(); String accName;
if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) {
accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
} else {
accName = "default";
}
final AccountSettings accSettings = getAccSettings(accName);
if (accSettings == null) {
return getCreateAccountIntent(data, accName);
}
String action = data.getAction(); String action = data.getAction();
if (OpenPgpApi.ACTION_SIGN.equals(action)) { if (OpenPgpApi.ACTION_SIGN.equals(action)) {
return signImpl(data, input, output, appSettings); return signImpl(data, input, output, accSettings);
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, false); return encryptAndSignImpl(data, input, output, accSettings, false);
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, true); return encryptAndSignImpl(data, input, output, accSettings, true);
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
return decryptAndVerifyImpl(data, input, output, appSettings); return decryptAndVerifyImpl(data, input, output, accSettings);
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
return getKeyImpl(data); return getKeyImpl(data);
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {

View File

@ -15,18 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote;
import java.util.ArrayList;
import java.util.Arrays;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@ -39,16 +28,24 @@ import android.content.pm.Signature;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
/** /**
* Abstract service class for remote APIs that handle app registration and user input. * Abstract service class for remote APIs that handle app registration and user input.
*/ */
public abstract class RemoteService extends Service { public abstract class RemoteService extends Service {
Context mContext; Context mContext;
private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
public Context getContext() { public Context getContext() {
return mContext; return mContext;
} }
@ -56,13 +53,10 @@ public abstract class RemoteService extends Service {
protected Intent isAllowed(Intent data) { protected Intent isAllowed(Intent data) {
try { try {
if (isCallerAllowed(false)) { if (isCallerAllowed(false)) {
return null; return null;
} else { } else {
String[] callingPackages = getPackageManager().getPackagesForUid( String packageName = getCurrentCallingPackage();
Binder.getCallingUid()); Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
// TODO: currently simply uses first entry
String packageName = callingPackages[0];
byte[] packageSignature; byte[] packageSignature;
try { try {
@ -84,7 +78,9 @@ public abstract class RemoteService extends Service {
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_REGISTER, intent, 0); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
// return PendingIntent to be executed by client // return PendingIntent to be executed by client
Intent result = new Intent(); Intent result = new Intent();
@ -98,10 +94,13 @@ public abstract class RemoteService extends Service {
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE); intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, getString(R.string.api_error_wrong_signature)); intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
getString(R.string.api_error_wrong_signature));
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data); intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_ERROR, intent, 0); PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
// return PendingIntent to be executed by client // return PendingIntent to be executed by client
Intent result = new Intent(); Intent result = new Intent();
@ -123,26 +122,57 @@ public abstract class RemoteService extends Service {
} }
/** /**
* Retrieves AppSettings from database for the application calling this remote service * Returns package name associated with the UID, which is assigned to the process that sent you the
* current transaction that is being processed :)
*
* @return package name
*/
private String getCurrentCallingPackage() {
// TODO:
// callingPackages contains more than one entry when sharedUserId has been used...
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
String currentPkg = callingPackages[0];
Log.d(Constants.TAG, "currentPkg: " + currentPkg);
return currentPkg;
}
/**
* Retrieves AccountSettings from database for the application calling this remote service
* *
* @return * @return
*/ */
protected AppSettings getAppSettings() { protected AccountSettings getAccSettings(String accountName) {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); String currentPkg = getCurrentCallingPackage();
Log.d(Constants.TAG, "accountName: " + accountName);
// get app settings for this package Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
for (int i = 0; i < callingPackages.length; i++) {
String currentPkg = callingPackages[i];
Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg); AccountSettings settings = ProviderHelper.getApiAccountSettings(this, uri);
AppSettings settings = ProviderHelper.getApiAppSettings(this, uri); return settings; // can be null!
}
if (settings != null) protected Intent getCreateAccountIntent(Intent data, String accountName) {
return settings; String packageName = getCurrentCallingPackage();
} Log.d(Constants.TAG, "accountName: " + accountName);
return null; Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RemoteServiceActivity.EXTRA_ACC_NAME, accountName);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
// return PendingIntent to be executed by client
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
return result;
} }
/** /**
@ -177,7 +207,7 @@ public abstract class RemoteService extends Service {
} }
} }
Log.d(Constants.TAG, "Caller is NOT allowed!"); Log.d(Constants.TAG, "Uid is NOT allowed!");
return false; return false;
} }
@ -189,7 +219,7 @@ public abstract class RemoteService extends Service {
* @throws WrongPackageSignatureException * @throws WrongPackageSignatureException
*/ */
private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException { private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
Log.d(Constants.TAG, "packageName: " + packageName); Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this); ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this);
Log.d(Constants.TAG, "allowed: " + allowedPkgs); Log.d(Constants.TAG, "allowed: " + allowedPkgs);
@ -217,6 +247,7 @@ public abstract class RemoteService extends Service {
} }
} }
Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName);
return false; return false;
} }

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote;
public class WrongPackageSignatureException extends Exception {
private static final long serialVersionUID = -8294642703122196028L;
public WrongPackageSignatureException(String message) {
super(message);
}
}

View File

@ -15,13 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote.ui;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@ -31,17 +25,25 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
public class AppSettingsActivity extends ActionBarActivity { import org.sufficientlysecure.keychain.Constants;
private Uri mAppUri; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.util.Log;
private AppSettingsFragment mSettingsFragment; public class AccountSettingsActivity extends ActionBarActivity {
private Uri mAccountUri;
private AccountSettingsFragment mAccountSettingsFragment;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done" custom action bar // Inflate a "Done" custom action bar
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save, ActionBarHelper.setOneButtonView(getSupportActionBar(),
R.string.api_settings_save, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -50,57 +52,58 @@ public class AppSettingsActivity extends ActionBarActivity {
} }
}); });
setContentView(R.layout.api_app_settings_activity); setContentView(R.layout.api_account_settings_activity);
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment); R.id.api_account_settings_fragment);
Intent intent = getIntent(); Intent intent = getIntent();
mAppUri = intent.getData(); mAccountUri = intent.getData();
if (mAppUri == null) { if (mAccountUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
finish(); finish();
return; return;
} else { } else {
Log.d(Constants.TAG, "uri: " + mAppUri); Log.d(Constants.TAG, "uri: " + mAccountUri);
loadData(mAppUri); loadData(savedInstanceState, mAccountUri);
} }
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.api_app_settings, menu); getMenuInflater().inflate(R.menu.api_account_settings, menu);
return true; return true;
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_api_settings_revoke: case R.id.menu_account_settings_delete:
revokeAccess(); deleteAccount();
return true; return true;
case R.id.menu_api_settings_cancel: case R.id.menu_account_settings_cancel:
finish(); finish();
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private void loadData(Uri appUri) { private void loadData(Bundle savedInstanceState, Uri accountUri) {
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri); // TODO: load this also like other fragment with newInstance arguments?
mSettingsFragment.setAppSettings(settings); AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri);
mAccountSettingsFragment.setAccSettings(settings);
} }
private void revokeAccess() { private void deleteAccount() {
if (getContentResolver().delete(mAppUri, null, null) <= 0) { if (getContentResolver().delete(mAccountUri, null, null) <= 0) {
throw new RuntimeException(); throw new RuntimeException();
} }
finish(); finish();
} }
private void save() { private void save() {
ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri); ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri);
finish(); finish();
} }

View File

@ -0,0 +1,176 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Spinner;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.ui.EditKeyActivity;
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
import org.sufficientlysecure.keychain.util.AlgorithmNames;
public class AccountSettingsFragment extends Fragment implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
// model
private AccountSettings mAccSettings;
// view
private TextView mAccNameView;
private Spinner mEncryptionAlgorithm;
private Spinner mHashAlgorithm;
private Spinner mCompression;
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
private BootstrapButton mCreateKeyButton;
KeyValueSpinnerAdapter mEncryptionAdapter;
KeyValueSpinnerAdapter mHashAdapter;
KeyValueSpinnerAdapter mCompressionAdapter;
public AccountSettings getAccSettings() {
return mAccSettings;
}
public void setAccSettings(AccountSettings accountSettings) {
this.mAccSettings = accountSettings;
mAccNameView.setText(accountSettings.getAccountName());
mSelectKeyFragment.selectKey(accountSettings.getKeyId());
mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(accountSettings
.getEncryptionAlgorithm()));
mHashAlgorithm.setSelection(mHashAdapter.getPosition(accountSettings.getHashAlgorithm()));
mCompression.setSelection(mCompressionAdapter.getPosition(accountSettings.getCompression()));
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_account_settings_fragment, container, false);
initView(view);
return view;
}
/**
* Set error String on key selection
*
* @param error
*/
public void setErrorOnSelectKeyFragment(String error) {
mSelectKeyFragment.setError(error);
}
private void initView(View view) {
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
R.id.api_account_settings_select_key_fragment);
mSelectKeyFragment.setCallback(this);
mAccNameView = (TextView) view.findViewById(R.id.api_account_settings_acc_name);
mEncryptionAlgorithm = (Spinner) view
.findViewById(R.id.api_account_settings_encryption_algorithm);
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_account_settings_hash_algorithm);
mCompression = (Spinner) view.findViewById(R.id.api_account_settings_compression);
mCreateKeyButton = (BootstrapButton) view.findViewById(R.id.api_account_settings_create_key);
mCreateKeyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createKey();
}
});
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getEncryptionNames());
mEncryptionAlgorithm.setAdapter(mEncryptionAdapter);
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAccSettings.setEncryptionAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
mHashAlgorithm.setAdapter(mHashAdapter);
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAccSettings.setHashAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getCompressionNames());
mCompression.setAdapter(mCompressionAdapter);
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAccSettings.setCompression((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
private void createKey() {
Intent intent = new Intent(getActivity(), EditKeyActivity.class);
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
// set default user id to account name TODO: not working currently in EditKey
intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, mAccSettings.getAccountName());
startActivityForResult(intent, 0);
}
/**
* callback from select secret key fragment
*/
@Override
public void onKeySelected(long secretKeyId) {
mAccSettings.setKeyId(secretKeyId);
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote.ui;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
import org.sufficientlysecure.keychain.util.Log;
public class AccountsListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final String ARG_DATA_URI = "uri";
// This is the Adapter being used to display the list's data.
AccountsAdapter mAdapter;
private Uri mDataUri;
/**
* Creates new instance of this fragment
*/
public static AccountsListFragment newInstance(Uri dataUri) {
AccountsListFragment frag = new AccountsListFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = super.onCreateView(inflater, container,
savedInstanceState);
ListView lv = (ListView) layout.findViewById(android.R.id.list);
ViewGroup parent = (ViewGroup) lv.getParent();
/*
* http://stackoverflow.com/a/15880684
* Remove ListView and add FixedListView in its place.
* This is done here programatically to be still able to use the progressBar of ListFragment.
*
* We want FixedListView to be able to put this ListFragment inside a ScrollView
*/
int lvIndex = parent.indexOfChild(lv);
parent.removeViewAt(lvIndex);
FixedListView newLv = new FixedListView(getActivity());
newLv.setId(android.R.id.list);
parent.addView(newLv, lvIndex, lv.getLayoutParams());
return layout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
getListView().setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String selectedAccountName = mAdapter.getItemAccountName(position);
Uri accountUri = mDataUri.buildUpon().appendEncodedPath(selectedAccountName).build();
Log.d(Constants.TAG, "accountUri: " + accountUri);
// edit account settings
Intent intent = new Intent(getActivity(), AccountSettingsActivity.class);
intent.setData(accountUri);
startActivity(intent);
}
});
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.api_settings_accounts_empty));
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new AccountsAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
// These are the Contacts rows that we will retrieve.
static final String[] PROJECTION = new String[]{
KeychainContract.ApiAccounts._ID, // 0
KeychainContract.ApiAccounts.ACCOUNT_NAME // 1
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null,
KeychainContract.ApiAccounts.ACCOUNT_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
private class AccountsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
public AccountsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
}
/**
* Similar to CursorAdapter.getItemId().
* Required to build Uris for api app view, which is not based on row ids
*
* @param position
* @return
*/
public String getItemAccountName(int position) {
if (mDataValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return mCursor.getString(1);
} else {
return null;
}
} else {
return null;
}
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.api_accounts_adapter_item_name);
String accountName = cursor.getString(1);
text.setText(accountName);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_accounts_adapter_list_item, null);
}
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent;
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;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.Log;
public class AppSettingsActivity extends ActionBarActivity {
private Uri mAppUri;
private AppSettingsFragment mSettingsFragment;
private AccountsListFragment mAccountsListFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
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);
setContentView(R.layout.api_app_settings_activity);
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment);
Intent intent = getIntent();
mAppUri = intent.getData();
if (mAppUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
finish();
return;
} else {
Log.d(Constants.TAG, "uri: " + mAppUri);
loadData(savedInstanceState, mAppUri);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.api_app_settings, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_api_settings_revoke:
revokeAccess();
return true;
case R.id.menu_api_settings_cancel:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void loadData(Bundle savedInstanceState, Uri appUri) {
// TODO: load this also like other fragment with newInstance arguments?
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
mSettingsFragment.setAppSettings(settings);
String appName;
PackageManager pm = getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(settings.getPackageName(), 0);
appName = (String) pm.getApplicationLabel(ai);
} catch (PackageManager.NameNotFoundException e) {
// fallback
appName = settings.getPackageName();
}
setTitle(appName);
Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build();
Log.d(Constants.TAG, "accountsUri: " + accountsUri);
startListFragment(savedInstanceState, accountsUri);
}
private void startListFragment(Bundle savedInstanceState, Uri dataUri) {
// 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
mAccountsListFragment = AccountsListFragment.newInstance(dataUri);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
private void revokeAccess() {
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
throw new RuntimeException();
}
finish();
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote.ui;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class AppSettingsFragment extends Fragment {
// model
private AppSettings mAppSettings;
// view
private TextView mAppNameView;
private ImageView mAppIconView;
private TextView mPackageName;
private TextView mPackageSignature;
public AppSettings getAppSettings() {
return mAppSettings;
}
public void setAppSettings(AppSettings appSettings) {
this.mAppSettings = appSettings;
updateView(appSettings);
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
return view;
}
private void updateView(AppSettings appSettings) {
// get application name and icon from package manager
String appName;
Drawable appIcon = null;
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
try {
ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (NameNotFoundException e) {
// fallback
appName = appSettings.getPackageName();
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
// advanced info: package name
mPackageName.setText(appSettings.getPackageName());
// advanced info: package signature SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(appSettings.getPackageSignature());
byte[] digest = md.digest();
String signature = new String(Hex.encode(digest));
mPackageSignature.setText(signature);
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, "Should not happen!", e);
}
}
}

View File

@ -15,14 +15,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote.ui;
import android.os.Bundle;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.DrawerActivity; import org.sufficientlysecure.keychain.ui.DrawerActivity;
import android.os.Bundle; public class AppsListActivity extends DrawerActivity {
public class RegisteredAppsListActivity extends DrawerActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {

View File

@ -15,14 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote.ui;
import org.sufficientlysecure.keychain.R; import android.content.Context;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import android.content.ContentUris;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -30,11 +28,22 @@ import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.TextView;
public class RegisteredAppsListFragment extends ListFragment implements import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.util.Log;
public class AppsListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data. // This is the Adapter being used to display the list's data.
@ -47,9 +56,10 @@ public class RegisteredAppsListFragment extends ListFragment implements
getListView().setOnItemClickListener(new OnItemClickListener() { getListView().setOnItemClickListener(new OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String selectedPackageName = mAdapter.getItemPackageName(position);
// edit app settings // edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class); Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id)); intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
startActivity(intent); startActivity(intent);
} }
}); });
@ -71,7 +81,10 @@ public class RegisteredAppsListFragment extends ListFragment implements
} }
// These are the Contacts rows that we will retrieve. // These are the Contacts rows that we will retrieve.
static final String[] PROJECTION = new String[] { ApiApps._ID, ApiApps.PACKAGE_NAME }; static final String[] PROJECTION = new String[]{
ApiApps._ID, // 0
ApiApps.PACKAGE_NAME // 1
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This // This is called when a new Loader needs to be created. This
@ -99,4 +112,65 @@ public class RegisteredAppsListFragment extends ListFragment implements
mAdapter.swapCursor(null); mAdapter.swapCursor(null);
} }
private class RegisteredAppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager mPM;
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
mPM = context.getApplicationContext().getPackageManager();
}
/**
* Similar to CursorAdapter.getItemId().
* Required to build Uris for api app view, which is not based on row ids
*
* @param position
* @return
*/
public String getItemPackageName(int position) {
if (mDataValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return mCursor.getString(1);
} else {
return null;
}
} else {
return null;
}
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
if (packageName != null) {
// get application name
try {
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
text.setText(mPM.getApplicationLabel(ai));
icon.setImageDrawable(mPM.getApplicationIcon(ai));
} catch (final PackageManager.NameNotFoundException e) {
// fallback
text.setText(packageName);
}
} else {
// fallback
text.setText(packageName);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
}
}
} }

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.sufficientlysecure.keychain.service.remote; package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -32,7 +32,10 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -42,6 +45,8 @@ import java.util.ArrayList;
public class RemoteServiceActivity extends ActionBarActivity { public class RemoteServiceActivity extends ActionBarActivity {
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX
+ "API_ACTIVITY_CREATE_ACCOUNT";
public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX
+ "API_ACTIVITY_CACHE_PASSPHRASE"; + "API_ACTIVITY_CACHE_PASSPHRASE";
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
@ -58,6 +63,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
// register action // register action
public static final String EXTRA_PACKAGE_NAME = "package_name"; public static final String EXTRA_PACKAGE_NAME = "package_name";
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature"; public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
// create acc action
public static final String EXTRA_ACC_NAME = "acc_name";
// select pub keys action // select pub keys action
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids";
@ -66,7 +73,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
public static final String EXTRA_ERROR_MESSAGE = "error_message"; public static final String EXTRA_ERROR_MESSAGE = "error_message";
// register view // register view
private AppSettingsFragment mSettingsFragment; private AppSettingsFragment mAppSettingsFragment;
// create acc view
private AccountSettingsFragment mAccSettingsFragment;
// select pub keys view // select pub keys view
private SelectPublicKeyFragment mSelectFragment; private SelectPublicKeyFragment mSelectFragment;
@ -86,29 +95,26 @@ public class RemoteServiceActivity extends ActionBarActivity {
if (ACTION_REGISTER.equals(action)) { if (ACTION_REGISTER.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME); final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
Log.d(Constants.TAG, "ACTION_REGISTER packageName: "+packageName);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, ActionBarHelper.setTwoButtonView(getSupportActionBar(),
R.string.api_register_allow, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// Allow // Allow
// user needs to select a key! ProviderHelper.insertApiApp(RemoteServiceActivity.this,
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { mAppSettingsFragment.getAppSettings());
mSettingsFragment.setErrorOnSelectKeyFragment(
getString(R.string.api_register_error_select_key));
} else {
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
mSettingsFragment.getAppSettings());
// give data through for new service call // give data through for new service call
Intent resultData = extras.getParcelable(EXTRA_DATA); Intent resultData = extras.getParcelable(EXTRA_DATA);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData); RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish(); RemoteServiceActivity.this.finish();
}
} }
}, R.string.api_register_disallow, new View.OnClickListener() { }, R.string.api_register_disallow, R.drawable.ic_action_cancel,
new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// Disallow // Disallow
@ -118,13 +124,58 @@ public class RemoteServiceActivity extends ActionBarActivity {
} }
); );
setContentView(R.layout.api_app_register_activity); setContentView(R.layout.api_remote_register_app);
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment); R.id.api_app_settings_fragment);
AppSettings settings = new AppSettings(packageName, packageSignature); AppSettings settings = new AppSettings(packageName, packageSignature);
mSettingsFragment.setAppSettings(settings); mAppSettingsFragment.setAppSettings(settings);
} else if (ACTION_CREATE_ACCOUNT.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
final String accName = extras.getString(EXTRA_ACC_NAME);
// 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 (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) {
mAccSettingsFragment.setErrorOnSelectKeyFragment(
getString(R.string.api_register_error_select_key));
} else {
ProviderHelper.insertApiAccount(RemoteServiceActivity.this,
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();
}
}
}, 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();
}
}
);
setContentView(R.layout.api_remote_create_account);
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_account_settings_fragment);
AccountSettings settings = new AccountSettings(accName);
mAccSettingsFragment.setAccSettings(settings);
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) { } else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
Intent resultData = extras.getParcelable(EXTRA_DATA); Intent resultData = extras.getParcelable(EXTRA_DATA);
@ -161,7 +212,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
} }
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(),
R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -173,7 +225,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
RemoteServiceActivity.this.setResult(RESULT_OK, resultData); RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish(); RemoteServiceActivity.this.finish();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel
@ -183,7 +235,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
} }
); );
setContentView(R.layout.api_app_select_pub_keys_activity); setContentView(R.layout.api_remote_select_pub_keys);
// set text on view // set text on view
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text);
@ -214,7 +266,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
String text = "<font color=\"red\">" + errorMessage + "</font>"; String text = "<font color=\"red\">" + errorMessage + "</font>";
// Inflate a "Done" custom action bar view // Inflate a "Done" custom action bar view
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setOneButtonView(getSupportActionBar(),
R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
@ -224,7 +277,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
} }
}); });
setContentView(R.layout.api_app_error_message); setContentView(R.layout.api_remote_error_message);
// set text on view // set text on view
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text); HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);

View File

@ -17,47 +17,6 @@
package org.sufficientlysecure.keychain.service; package org.sufficientlysecure.keychain.service;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.HkpKeyServer;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.app.IntentService; import android.app.IntentService;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -67,12 +26,32 @@ import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import org.spongycastle.openpgp.*;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.*;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.*;
import java.io.*;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
/** /**
* This Service contains all important long lasting operations for APG. It receives Intents with * This Service contains all important long lasting operations for APG. It receives Intents with
* data from the activities or other apps, queues these intents, executes them, and stops itself * data from the activities or other apps, queues these intents, executes them, and stops itself
* after doing them. * after doing them.
*/ */
public class KeychainIntentService extends IntentService implements ProgressDialogUpdater { public class KeychainIntentService extends IntentService
implements ProgressDialogUpdater, KeychainServiceListener {
/* extras that can be given by intent */ /* extras that can be given by intent */
public static final String EXTRA_MESSENGER = "messenger"; public static final String EXTRA_MESSENGER = "messenger";
@ -159,6 +138,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// sign key // sign key
public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id"; public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";
public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id";
public static final String CERTIFY_KEY_UIDS = "sign_key_uids";
/* /*
* possible data keys as result send over messenger * possible data keys as result send over messenger
@ -324,8 +304,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
builder.enableAsciiArmorOutput(useAsciiArmor) builder.enableAsciiArmorOutput(useAsciiArmor)
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.signatureKeyId(secretKeyId) .signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm()) .signatureHashAlgorithm(
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().generateSignature(); builder.build().generateSignature();
} else if (signOnly) { } else if (signOnly) {
@ -333,21 +315,26 @@ public class KeychainIntentService extends IntentService implements ProgressDial
builder.enableAsciiArmorOutput(useAsciiArmor) builder.enableAsciiArmorOutput(useAsciiArmor)
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.signatureKeyId(secretKeyId) .signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm()) .signatureHashAlgorithm(
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().execute(); builder.build().execute();
} else { } else {
Log.d(Constants.TAG, "encrypt..."); Log.d(Constants.TAG, "encrypt...");
builder.enableAsciiArmorOutput(useAsciiArmor) builder.enableAsciiArmorOutput(useAsciiArmor)
.compressionId(compressionId) .compressionId(compressionId)
.symmetricEncryptionAlgorithm(Preferences.getPreferences(this).getDefaultEncryptionAlgorithm()) .symmetricEncryptionAlgorithm(
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures()) .signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.encryptionKeyIds(encryptionKeyIds) .encryptionKeyIds(encryptionKeyIds)
.encryptionPassphrase(encryptionPassphrase) .encryptionPassphrase(encryptionPassphrase)
.signatureKeyId(secretKeyId) .signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm()) .signatureHashAlgorithm(
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().execute(); builder.build().execute();
} }
@ -586,13 +573,24 @@ public class KeychainIntentService extends IntentService implements ProgressDial
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE); String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
/* Operation */ /* Operation */
int keysTotal = 2;
int keysCreated = 0;
setProgress(
getApplicationContext().getResources().
getQuantityString(R.plurals.progress_generating, keysTotal),
keysCreated,
keysTotal);
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa, PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
4096, passphrase, true); 4096, passphrase, true);
keysCreated++;
setProgress(keysCreated, keysTotal);
PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa, PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
4096, passphrase, false); 4096, passphrase, false);
keysCreated++;
setProgress(keysCreated, keysTotal);
// TODO: default to one master for cert, one sub for encrypt and one sub // TODO: default to one master for cert, one sub for encrypt and one sub
// for sign // for sign
@ -652,14 +650,11 @@ public class KeychainIntentService extends IntentService implements ProgressDial
if (data.containsKey(EXPORT_KEY_TYPE)) { if (data.containsKey(EXPORT_KEY_TYPE)) {
keyType = data.getInt(EXPORT_KEY_TYPE); keyType = data.getInt(EXPORT_KEY_TYPE);
} }
long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
String outputFile = data.getString(EXPORT_FILENAME); String outputFile = data.getString(EXPORT_FILENAME);
// If not exporting all keys get the masterKeyIds of the keys to export from the intent
boolean exportAll = data.getBoolean(EXPORT_ALL); boolean exportAll = data.getBoolean(EXPORT_ALL);
long keyRingMasterKeyId = -1;
if (!exportAll) {
keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID);
}
/* Operation */ /* Operation */
@ -668,27 +663,42 @@ public class KeychainIntentService extends IntentService implements ProgressDial
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
} }
// OutputStream ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
FileOutputStream outStream = new FileOutputStream(outputFile); ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>();
ArrayList<Long> allPublicMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this);
ArrayList<Long> allSecretMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this);
ArrayList<Long> keyRingMasterKeyIds = new ArrayList<Long>();
if (exportAll) { if (exportAll) {
// get all key ring row ids based on export type // get all public key ring MasterKey ids
if (keyType == Id.type.public_key || keyType == Id.type.public_secret_key) {
if (keyType == Id.type.public_key) { publicMasterKeyIds = allPublicMasterKeyIds;
keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this); }
} else { // get all secret key ring MasterKey ids
keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this); if (keyType == Id.type.secret_key || keyType == Id.type.public_secret_key) {
secretMasterKeyIds = allSecretMasterKeyIds;
} }
} else { } else {
keyRingMasterKeyIds.add(keyRingMasterKeyId);
for (long masterKeyId : masterKeyIds) {
if ((keyType == Id.type.public_key || keyType == Id.type.public_secret_key)
&& allPublicMasterKeyIds.contains(masterKeyId)) {
publicMasterKeyIds.add(masterKeyId);
}
if ((keyType == Id.type.secret_key || keyType == Id.type.public_secret_key)
&& allSecretMasterKeyIds.contains(masterKeyId)) {
secretMasterKeyIds.add(masterKeyId);
}
}
} }
Bundle resultData = new Bundle(); PgpImportExport pgpImportExport = new PgpImportExport(this, this, this);
Bundle resultData = pgpImportExport
.exportKeyRings(publicMasterKeyIds, secretMasterKeyIds,
new FileOutputStream(outputFile));
PgpImportExport pgpImportExport = new PgpImportExport(this, this); if (mIsCanceled) {
resultData = pgpImportExport boolean isDeleted = new File(outputFile).delete();
.exportKeyRings(keyRingMasterKeyIds, keyType, outStream); }
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) { } catch (Exception e) {
@ -724,48 +734,58 @@ public class KeychainIntentService extends IntentService implements ProgressDial
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST); ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
String keyServer = data.getString(DOWNLOAD_KEY_SERVER); String keyServer = data.getString(DOWNLOAD_KEY_SERVER);
// TODO: add extra which requires fingerprint suport and force verification!
// only supported by newer sks keyserver versions
// this downloads the keys and places them into the ImportKeysListEntry entries // this downloads the keys and places them into the ImportKeysListEntry entries
HkpKeyServer server = new HkpKeyServer(keyServer); HkpKeyServer server = new HkpKeyServer(keyServer);
for (ImportKeysListEntry entry : entries) { for (ImportKeysListEntry entry : entries) {
byte[] downloadedKey = server.get(entry.getKeyId()).getBytes(); // if available use complete fingerprint for get request
byte[] downloadedKeyBytes;
if (entry.getFingerPrintHex() != null) {
downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes();
} else {
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
}
/** // create PGPKeyRing object based on downloaded armored key
* TODO: copied from ImportKeysListLoader PGPKeyRing downloadedKey = null;
* BufferedInputStream bufferedInput =
* new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
* this parses the downloaded key if (bufferedInput.available() > 0) {
*/ InputStream in = PGPUtil.getDecoderStream(bufferedInput);
// need to have access to the bufferedInput, so we can reuse it for the possible PGPObjectFactory objectFactory = new PGPObjectFactory(in);
// PGPObject chunks after the first one, e.g. files with several consecutive ASCII
// armor blocks
BufferedInputStream bufferedInput = new BufferedInputStream(new ByteArrayInputStream(downloadedKey));
try {
// read all available blocks... (asc files can contain many blocks with BEGIN END) // get first object in block
while (bufferedInput.available() > 0) { Object obj;
InputStream in = PGPUtil.getDecoderStream(bufferedInput); if ((obj = objectFactory.nextObject()) != null) {
PGPObjectFactory objectFactory = new PGPObjectFactory(in); Log.d(Constants.TAG, "Found class: " + obj.getClass());
// go through all objects in this block if (obj instanceof PGPKeyRing) {
Object obj; downloadedKey = (PGPKeyRing) obj;
while ((obj = objectFactory.nextObject()) != null) { } else {
Log.d(Constants.TAG, "Found class: " + obj.getClass()); throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
if (obj instanceof PGPKeyRing) {
PGPKeyRing newKeyring = (PGPKeyRing) obj;
entry.setBytes(newKeyring.getEncoded());
} else {
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
}
} }
} }
} catch (Exception e) {
Log.e(Constants.TAG, "Exception on parsing key file!", e);
} }
// verify downloaded key by comparing fingerprints
if (entry.getFingerPrintHex() != null) {
String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex(downloadedKey.getPublicKey().getFingerprint());
if (downloadedKeyFp.equals(entry.getFingerPrintHex())) {
Log.d(Constants.TAG, "fingerprint of downloaded key is the same as the requested fingerprint!");
} else {
throw new PgpGeneralException("fingerprint of downloaded key is NOT the same as the requested fingerprint!");
}
}
// save key bytes in entry object for doing the
// actual import afterwards
entry.setBytes(downloadedKey.getEncoded());
} }
Intent importIntent = new Intent(this, KeychainIntentService.class); Intent importIntent = new Intent(this, KeychainIntentService.class);
importIntent.setAction(ACTION_IMPORT_KEYRING); importIntent.setAction(ACTION_IMPORT_KEYRING);
Bundle importData = new Bundle(); Bundle importData = new Bundle();
@ -786,6 +806,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
/* Input */ /* Input */
long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID); long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID); long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
/* Operation */ /* Operation */
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
@ -793,7 +814,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this); PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId, PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
signaturePassPhrase); userIds, signaturePassPhrase);
// store the signed key in our local cache // store the signed key in our local cache
PgpImportExport pgpImportExport = new PgpImportExport(this, null); PgpImportExport pgpImportExport = new PgpImportExport(this, null);
@ -811,10 +832,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
private void sendErrorToHandler(Exception e) { private void sendErrorToHandler(Exception e) {
// Service was canceled. Do not send error to handler. // Service was canceled. Do not send error to handler.
if (this.mIsCanceled) if (this.mIsCanceled) {
return; return;
}
Log.e(Constants.TAG, "ApgService Exception: ", e); Log.e(Constants.TAG, "KeychainIntentService Exception: ", e);
e.printStackTrace(); e.printStackTrace();
Bundle data = new Bundle(); Bundle data = new Bundle();
@ -824,9 +845,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) { private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) {
// Service was canceled. Do not send message to handler. // Service was canceled. Do not send message to handler.
if (this.mIsCanceled) if (this.mIsCanceled) {
return; return;
}
Message msg = Message.obtain(); Message msg = Message.obtain();
msg.arg1 = arg1; msg.arg1 = arg1;
if (arg2 != null) { if (arg2 != null) {
@ -877,4 +898,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public void setProgress(int progress, int max) { public void setProgress(int progress, int max) {
setProgress(null, progress, max); setProgress(null, progress, max);
} }
@Override
public boolean hasServiceStopped() {
return mIsCanceled;
}
} }

View File

@ -17,11 +17,7 @@
package org.sufficientlysecure.keychain.service; package org.sufficientlysecure.keychain.service;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.R;
import android.app.Activity; import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -29,6 +25,8 @@ import android.os.Message;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.widget.Toast; import android.widget.Toast;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
public class KeychainIntentServiceHandler extends Handler { public class KeychainIntentServiceHandler extends Handler {
@ -51,25 +49,31 @@ public class KeychainIntentServiceHandler extends Handler {
this.mActivity = activity; this.mActivity = activity;
} }
public KeychainIntentServiceHandler(Activity activity, ProgressDialogFragment progressDialogFragment) { public KeychainIntentServiceHandler(Activity activity,
ProgressDialogFragment progressDialogFragment) {
this.mActivity = activity; this.mActivity = activity;
this.mProgressDialogFragment = progressDialogFragment; this.mProgressDialogFragment = progressDialogFragment;
} }
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) { public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
this(activity, progressDialogMessageId, progressDialogStyle, false, null); int progressDialogStyle) {
this(activity, progressDialogMessage, progressDialogStyle, false, null);
} }
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
int progressDialogStyle, boolean cancelable, int progressDialogStyle, boolean cancelable,
OnCancelListener onCancelListener) { OnCancelListener onCancelListener) {
this.mActivity = activity; this.mActivity = activity;
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId, this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
progressDialogStyle, cancelable, onCancelListener); progressDialogMessage,
progressDialogStyle,
cancelable,
onCancelListener);
} }
public void showProgressDialog(FragmentActivity activity) { public void showProgressDialog(FragmentActivity activity) {
// TODO: This is a hack!, see http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult // TODO: This is a hack!, see
// http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
final FragmentManager manager = activity.getSupportFragmentManager(); final FragmentManager manager = activity.getSupportFragmentManager();
Handler handler = new Handler(); Handler handler = new Handler();
handler.post(new Runnable() { handler.post(new Runnable() {
@ -84,43 +88,43 @@ public class KeychainIntentServiceHandler extends Handler {
Bundle data = message.getData(); Bundle data = message.getData();
switch (message.arg1) { switch (message.arg1) {
case MESSAGE_OKAY: case MESSAGE_OKAY:
mProgressDialogFragment.dismissAllowingStateLoss(); mProgressDialogFragment.dismissAllowingStateLoss();
break; break;
case MESSAGE_EXCEPTION: case MESSAGE_EXCEPTION:
mProgressDialogFragment.dismissAllowingStateLoss(); mProgressDialogFragment.dismissAllowingStateLoss();
// show error from service // show error from service
if (data.containsKey(DATA_ERROR)) { if (data.containsKey(DATA_ERROR)) {
Toast.makeText(mActivity, Toast.makeText(mActivity,
mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)), mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
}
break;
case MESSAGE_UPDATE_PROGRESS:
if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
// update progress from service
if (data.containsKey(DATA_MESSAGE)) {
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
} else if (data.containsKey(DATA_MESSAGE_ID)) {
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
} else {
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
data.getInt(DATA_PROGRESS_MAX));
} }
}
break; break;
default: case MESSAGE_UPDATE_PROGRESS:
break; if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
// update progress from service
if (data.containsKey(DATA_MESSAGE)) {
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
} else if (data.containsKey(DATA_MESSAGE_ID)) {
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
} else {
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
data.getInt(DATA_PROGRESS_MAX));
}
}
break;
default:
break;
} }
} }
} }

View File

@ -17,10 +17,16 @@
package org.sufficientlysecure.keychain.service; package org.sufficientlysecure.keychain.service;
import java.util.Date; import android.app.AlarmManager;
import java.util.HashMap; import android.app.PendingIntent;
import java.util.Iterator; import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.*;
import android.util.Log;
import android.support.v4.util.LongSparseArray;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
@ -33,28 +39,13 @@ import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import android.app.AlarmManager; import java.util.Date;
import android.app.PendingIntent; import java.util.Iterator;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
/** /**
* This service runs in its own process, but is available to all other processes as the main * This service runs in its own process, but is available to all other processes as the main
* passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for * passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for
* convenience. * convenience.
*
*/ */
public class PassphraseCacheService extends Service { public class PassphraseCacheService extends Service {
public static final String TAG = Constants.TAG + ": PassphraseCacheService"; public static final String TAG = Constants.TAG + ": PassphraseCacheService";
@ -77,7 +68,7 @@ public class PassphraseCacheService extends Service {
private BroadcastReceiver mIntentReceiver; private BroadcastReceiver mIntentReceiver;
private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>(); private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
Context mContext; Context mContext;
@ -215,17 +206,17 @@ public class PassphraseCacheService extends Service {
.getPGPSecretKeyRingByKeyId(context, secretKeyId); .getPGPSecretKeyRingByKeyId(context, secretKeyId);
PGPSecretKey secretKey = null; PGPSecretKey secretKey = null;
boolean foundValidKey = false; boolean foundValidKey = false;
for (Iterator keys = secRing.getSecretKeys(); keys.hasNext();) { for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) {
secretKey = (PGPSecretKey)keys.next(); secretKey = (PGPSecretKey) keys.next();
if (!secretKey.isPrivateKeyEmpty()) { if (!secretKey.isPrivateKeyEmpty()) {
foundValidKey = true; foundValidKey = true;
break; break;
} }
} }
if (!foundValidKey) if (!foundValidKey) {
return false; return false;
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
"SC").build("".toCharArray()); "SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor); PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
@ -347,7 +338,7 @@ public class PassphraseCacheService extends Service {
Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!"); Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
// stop whole service if no cached passphrases remaining // stop whole service if no cached passphrases remaining
if (mPassphraseCache.isEmpty()) { if (mPassphraseCache.size() == 0) {
Log.d(TAG, "No passphrases remaining in memory, stopping service!"); Log.d(TAG, "No passphrases remaining in memory, stopping service!");
stopSelf(); stopSelf();
} }

View File

@ -1,243 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service.remote;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
import org.sufficientlysecure.keychain.util.AlgorithmNames;
import org.sufficientlysecure.keychain.util.Log;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
public class AppSettingsFragment extends Fragment implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
// model
private AppSettings appSettings;
// view
private LinearLayout mAdvancedSettingsContainer;
private BootstrapButton mAdvancedSettingsButton;
private TextView mAppNameView;
private ImageView mAppIconView;
private Spinner mEncryptionAlgorithm;
private Spinner mHashAlgorithm;
private Spinner mCompression;
private TextView mPackageName;
private TextView mPackageSignature;
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
KeyValueSpinnerAdapter encryptionAdapter;
KeyValueSpinnerAdapter hashAdapter;
KeyValueSpinnerAdapter compressionAdapter;
public AppSettings getAppSettings() {
return appSettings;
}
public void setAppSettings(AppSettings appSettings) {
this.appSettings = appSettings;
setPackage(appSettings.getPackageName());
mPackageName.setText(appSettings.getPackageName());
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(appSettings.getPackageSignature());
byte[] digest = md.digest();
String signature = new String(Hex.encode(digest));
mPackageSignature.setText(signature);
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, "Should not happen!", e);
}
mSelectKeyFragment.selectKey(appSettings.getKeyId());
mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings
.getEncryptionAlgorithm()));
mHashAlgorithm.setSelection(hashAdapter.getPosition(appSettings.getHashAlgorithm()));
mCompression.setSelection(compressionAdapter.getPosition(appSettings.getCompression()));
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
initView(view);
return view;
}
/**
* Set error String on key selection
*
* @param error
*/
public void setErrorOnSelectKeyFragment(String error) {
mSelectKeyFragment.setError(error);
}
private void initView(View view) {
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
R.id.api_app_settings_select_key_fragment);
mSelectKeyFragment.setCallback(this);
mAdvancedSettingsButton = (BootstrapButton) view
.findViewById(R.id.api_app_settings_advanced_button);
mAdvancedSettingsContainer = (LinearLayout) view
.findViewById(R.id.api_app_settings_advanced);
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
mEncryptionAlgorithm = (Spinner) view
.findViewById(R.id.api_app_settings_encryption_algorithm);
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm);
mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression);
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
encryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getEncryptionNames());
mEncryptionAlgorithm.setAdapter(encryptionAdapter);
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
appSettings.setEncryptionAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
hashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
mHashAlgorithm.setAdapter(hashAdapter);
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
appSettings.setHashAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
compressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getCompressionNames());
mCompression.setAdapter(compressionAdapter);
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
appSettings.setCompression((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f);
visibleAnimation.setDuration(250);
final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f);
invisibleAnimation.setDuration(250);
// TODO: Better: collapse/expand animation
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
// Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
// Animation.RELATIVE_TO_SELF, 0.0f);u
// animation2.setDuration(150);
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
mAdvancedSettingsContainer.setVisibility(View.GONE);
mAdvancedSettingsButton.setText(getString(R.string.api_settings_show_advanced));
mAdvancedSettingsButton.setLeftIcon("fa-caret-up");
} else {
mAdvancedSettingsContainer.startAnimation(visibleAnimation);
mAdvancedSettingsContainer.setVisibility(View.VISIBLE);
mAdvancedSettingsButton.setText(getString(R.string.api_settings_hide_advanced));
mAdvancedSettingsButton.setLeftIcon("fa-caret-down");
}
}
});
}
private void setPackage(String packageName) {
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
// get application name and icon from package manager
String appName = null;
Drawable appIcon = null;
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
appName = (String) pm.getApplicationLabel(ai);
appIcon = pm.getApplicationIcon(ai);
} catch (final NameNotFoundException e) {
// fallback
appName = packageName;
}
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
}
/**
* callback from select secret key fragment
*/
@Override
public void onKeySelected(long secretKeyId) {
appSettings.setKeyId(secretKeyId);
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service.remote;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
public class RegisteredAppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager pm;
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
pm = context.getApplicationContext().getPackageManager();
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
if (packageName != null) {
// get application name
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
text.setText(pm.getApplicationLabel(ai));
icon.setImageDrawable(pm.getApplicationIcon(ai));
} catch (final NameNotFoundException e) {
// fallback
text.setText(packageName);
}
} else {
// fallback
text.setText(packageName);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
}
}

View File

@ -1,10 +0,0 @@
package org.sufficientlysecure.keychain.service.remote;
public class WrongPackageSignatureException extends Exception {
private static final long serialVersionUID = -8294642703122196028L;
public WrongPackageSignatureException(String message) {
super(message);
}
}

View File

@ -17,47 +17,47 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.Iterator;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.ArrayAdapter; import android.widget.*;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Spinner;
import android.widget.Toast;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
/** /**
* Signs the specified public key with the specified secret master key * Signs the specified public key with the specified secret master key
*/ */
public class CertifyKeyActivity extends ActionBarActivity implements public class CertifyKeyActivity extends ActionBarActivity implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
private BootstrapButton mSignButton; private BootstrapButton mSignButton;
private CheckBox mUploadKeyCheckbox; private CheckBox mUploadKeyCheckbox;
private Spinner mSelectKeyserverSpinner; private Spinner mSelectKeyserverSpinner;
@ -68,6 +68,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements
private long mPubKeyId = 0; private long mPubKeyId = 0;
private long mMasterKeyId = 0; private long mMasterKeyId = 0;
private ListView mUserIds;
private ViewKeyUserIdsAdapter mUserIdsAdapter;
private static final int LOADER_ID_KEYRING = 0;
private static final int LOADER_ID_USER_IDS = 1;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -87,7 +93,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver); mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, Preferences.getPreferences(this) android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
.getKeyServers()); .getKeyServers());
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSelectKeyserverSpinner.setAdapter(adapter); mSelectKeyserverSpinner.setAdapter(adapter);
@ -131,9 +137,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements
finish(); finish();
return; return;
} }
Log.e(Constants.TAG, "uri: " + mDataUri);
PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri); PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri);
mUserIds = (ListView) findViewById(R.id.user_ids);
mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true);
mUserIds.setAdapter(mUserIdsAdapter);
getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
if (signKey != null) { if (signKey != null) {
mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID(); mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();
} }
@ -144,6 +159,78 @@ public class CertifyKeyActivity extends ActionBarActivity implements
} }
} }
static final String[] KEYRING_PROJECTION =
new String[] {
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.Keys.FINGERPRINT,
KeychainContract.UserIds.USER_ID
};
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_FINGERPRINT = 2;
static final int INDEX_USER_ID = 3;
static final String[] USER_IDS_PROJECTION =
new String[]{
KeychainContract.UserIds._ID,
KeychainContract.UserIds.USER_ID,
KeychainContract.UserIds.RANK
};
static final String USER_IDS_SORT_ORDER =
KeychainContract.UserIds.RANK + " ASC";
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch(id) {
case LOADER_ID_KEYRING:
return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null);
case LOADER_ID_USER_IDS: {
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER);
}
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch(loader.getId()) {
case LOADER_ID_KEYRING:
// the first key here is our master key
if (data.moveToFirst()) {
// TODO: put findViewById in onCreate!
long keyId = data.getLong(INDEX_MASTER_KEY_ID);
String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(keyId);
((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
String mainUserId = data.getString(INDEX_USER_ID);
((TextView) findViewById(R.id.main_user_id)).setText(mainUserId);
byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
if (fingerprintBlob == null) {
// FALLBACK for old database entries
fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri);
}
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
((TextView) findViewById(R.id.fingerprint)).setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
}
break;
case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(data);
break;
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch(loader.getId()) {
case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(null);
break;
}
}
private void showPassphraseDialog(final long secretKeyId) { private void showPassphraseDialog(final long secretKeyId) {
// Message is received after passphrase is cached // Message is received after passphrase is cached
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@ -179,6 +266,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
// if we have already signed this key, dont bother doing it again // if we have already signed this key, dont bother doing it again
boolean alreadySigned = false; boolean alreadySigned = false;
/* todo: reconsider this at a later point when certs are in the db
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures(); Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();
while (itr.hasNext()) { while (itr.hasNext()) {
@ -188,6 +276,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
break; break;
} }
} }
*/
if (!alreadySigned) { if (!alreadySigned) {
/* /*
@ -215,6 +304,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
* kicks off the actual signing process on a background thread * kicks off the actual signing process on a background thread
*/ */
private void startSigning() { private void startSigning() {
// Bail out if there is not at least one user id selected
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
if(userIds.isEmpty()) {
Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
Toast.LENGTH_SHORT).show();
return;
}
// Send all information needed to service to sign key in other thread // Send all information needed to service to sign key in other thread
Intent intent = new Intent(this, KeychainIntentService.class); Intent intent = new Intent(this, KeychainIntentService.class);
@ -225,14 +323,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId); data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);
data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId); data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId);
data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after signing is done in ApgService // Message is received after signing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_signing, ProgressDialog.STYLE_SPINNER) { getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -249,7 +348,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
finish(); finish();
} }
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -281,11 +380,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after uploading is done in ApgService // Message is received after uploading is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -295,7 +394,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -17,37 +17,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.regex.Matcher;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
@ -59,16 +28,34 @@ import android.os.Messenger;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.CheckBox; import android.widget.*;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg; import com.devspark.appmsg.AppMsg;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.io.*;
import java.util.regex.Matcher;
@SuppressLint("NewApi") @SuppressLint("NewApi")
public class DecryptActivity extends DrawerActivity { public class DecryptActivity extends DrawerActivity {
@ -364,7 +351,7 @@ public class DecryptActivity extends DrawerActivity {
} }
} else { } else {
Log.e(Constants.TAG, Log.e(Constants.TAG,
"Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!"); "Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!");
Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG) Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
.show(); .show();
// end activity // end activity
@ -383,7 +370,7 @@ public class DecryptActivity extends DrawerActivity {
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
filename = filename.substring(0, filename.length() - 4); filename = filename.substring(0, filename.length() - 4);
} }
mOutputFilename = Constants.path.APP_DIR + "/" + filename; mOutputFilename = Constants.Path.APP_DIR + "/" + filename;
} }
private void updateSource() { private void updateSource() {
@ -456,8 +443,7 @@ public class DecryptActivity extends DrawerActivity {
getDecryptionKeyFromInputStream(); getDecryptionKeyFromInputStream();
// if we need a symmetric passphrase or a passphrase to use a secret key ask for it // if we need a symmetric passphrase or a passphrase to use a secret key ask for it
if (mSecretKeyId == Id.key.symmetric if (mAssumeSymmetricEncryption || PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
|| PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
showPassphraseDialog(); showPassphraseDialog();
} else { } else {
if (mDecryptTarget == Id.target.file) { if (mDecryptTarget == Id.target.file) {
@ -507,6 +493,7 @@ public class DecryptActivity extends DrawerActivity {
* TODO: Rework function, remove global variables * TODO: Rework function, remove global variables
*/ */
private void getDecryptionKeyFromInputStream() { private void getDecryptionKeyFromInputStream() {
mAssumeSymmetricEncryption = false;
InputStream inStream = null; InputStream inStream = null;
if (mContentUri != null) { if (mContentUri != null) {
try { try {
@ -546,7 +533,6 @@ public class DecryptActivity extends DrawerActivity {
if (mSecretKeyId == Id.key.none) { if (mSecretKeyId == Id.key.none) {
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found)); throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
} }
mAssumeSymmetricEncryption = false;
} catch (NoAsymmetricEncryptionException e) { } catch (NoAsymmetricEncryptionException e) {
if (inStream.markSupported()) { if (inStream.markSupported()) {
inStream.reset(); inStream.reset();
@ -559,6 +545,7 @@ public class DecryptActivity extends DrawerActivity {
mAssumeSymmetricEncryption = true; mAssumeSymmetricEncryption = true;
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(Constants.TAG, "error while reading decryption key from input stream", e);
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show(); AppMsg.STYLE_ALERT).show();
} }
@ -644,11 +631,11 @@ public class DecryptActivity extends DrawerActivity {
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after encrypting is done in ApgService // Message is received after encrypting is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -744,8 +731,6 @@ public class DecryptActivity extends DrawerActivity {
} }
} }
} }
;
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -17,30 +17,21 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat; import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater; import android.view.*;
import android.view.Menu; import android.widget.*;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.FontAwesomeText; import com.beardedhen.androidbootstrap.FontAwesomeText;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
public class DrawerActivity extends ActionBarActivity { public class DrawerActivity extends ActionBarActivity {
private DrawerLayout mDrawerLayout; private DrawerLayout mDrawerLayout;
@ -49,10 +40,8 @@ public class DrawerActivity extends ActionBarActivity {
private CharSequence mDrawerTitle; private CharSequence mDrawerTitle;
private CharSequence mTitle; private CharSequence mTitle;
private boolean mIsDrawerLocked = false;
private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class,
EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class,
KeyListSecretActivity.class, RegisteredAppsListActivity.class };
private Class mSelectedItem; private Class mSelectedItem;
private static final int MENU_ID_PREFERENCE = 222; private static final int MENU_ID_PREFERENCE = 222;
@ -62,18 +51,29 @@ public class DrawerActivity extends ActionBarActivity {
mDrawerTitle = getString(R.string.app_name); mDrawerTitle = getString(R.string.app_name);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer); mDrawerList = (ListView) findViewById(R.id.left_drawer);
ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame);
int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin;
int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size);
int errorInMarginAllowed = 5;
// set a custom shadow that overlays the main content when the drawer // if the left margin of the loaded layout is close to the
// opens // one used in tablets then set drawer as open and locked
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); if( Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList);
mDrawerLayout.setScrimColor(Color.TRANSPARENT);
mIsDrawerLocked = true;
} else {
// set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
mIsDrawerLocked = false;
}
NavItem mItemIconTexts[] = new NavItem[] { NavItem mItemIconTexts[] = new NavItem[]{
new NavItem("fa-user", getString(R.string.nav_contacts)), new NavItem("fa-user", getString(R.string.nav_contacts)),
new NavItem("fa-lock", getString(R.string.nav_encrypt)), new NavItem("fa-lock", getString(R.string.nav_encrypt)),
new NavItem("fa-unlock", getString(R.string.nav_decrypt)), new NavItem("fa-unlock", getString(R.string.nav_decrypt)),
new NavItem("fa-download", getString(R.string.nav_import)), new NavItem("fa-download", getString(R.string.nav_import)),
new NavItem("fa-key", getString(R.string.nav_secret_keys)), new NavItem("fa-android", getString(R.string.nav_apps))};
new NavItem("fa-android", getString(R.string.nav_apps)) };
mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item, mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item,
mItemIconTexts)); mItemIconTexts));
@ -81,32 +81,24 @@ public class DrawerActivity extends ActionBarActivity {
mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
// enable ActionBar app icon to behave as action to toggle nav drawer // enable ActionBar app icon to behave as action to toggle nav drawer
getSupportActionBar().setDisplayHomeAsUpEnabled(true); // if the drawer is not locked
getSupportActionBar().setHomeButtonEnabled(true); if ( !mIsDrawerLocked ) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// ActionBarDrawerToggle ties together the the proper interactions // ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon // between the sliding drawer and the action bar app icon
mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */ mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */ R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */ R.string.drawer_close /* "close drawer" description for accessibility */
) { ) {
public void onDrawerClosed(View view) { public void onDrawerClosed(View view) {
getSupportActionBar().setTitle(mTitle); getSupportActionBar().setTitle(mTitle);
// creates call to onPrepareOptionsMenu()
supportInvalidateOptionsMenu();
// call intent activity if selected callIntentForDrawerItem(mSelectedItem);
if(mSelectedItem != null) {
finish();
overridePendingTransition(0, 0);
Intent intent = new Intent(DrawerActivity.this, mSelectedItem);
startActivity(intent);
// disable animation of activity start
overridePendingTransition(0, 0);
}
} }
public void onDrawerOpened(View drawerView) { public void onDrawerOpened(View drawerView) {
@ -116,13 +108,40 @@ public class DrawerActivity extends ActionBarActivity {
supportInvalidateOptionsMenu(); supportInvalidateOptionsMenu();
} }
}; };
mDrawerLayout.setDrawerListener(mDrawerToggle);
if ( !mIsDrawerLocked ) {
mDrawerLayout.setDrawerListener(mDrawerToggle);
} else {
// If the drawer is locked open make it un-focusable
// so that it doesn't consume all the Back button presses
mDrawerLayout.setFocusableInTouchMode(false);
}
// if (savedInstanceState == null) { // if (savedInstanceState == null) {
// selectItem(0); // selectItem(0);
// } // }
} }
/**
* Uses startActivity to call the Intent of the given class
* @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.*
*/
public void callIntentForDrawerItem(Class drawerItem) {
// creates call to onPrepareOptionsMenu()
supportInvalidateOptionsMenu();
// call intent activity if selected
if (drawerItem != null) {
finish();
overridePendingTransition(0, 0);
Intent intent = new Intent(this, drawerItem);
startActivity(intent);
// disable animation of activity start
overridePendingTransition(0, 0);
}
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences); menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences);
@ -150,18 +169,18 @@ public class DrawerActivity extends ActionBarActivity {
} }
switch (item.getItemId()) { switch (item.getItemId()) {
case MENU_ID_PREFERENCE: { case MENU_ID_PREFERENCE: {
Intent intent = new Intent(this, PreferencesActivity.class); Intent intent = new Intent(this, PreferencesActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
} }
case MENU_ID_HELP: { case MENU_ID_HELP: {
Intent intent = new Intent(this, HelpActivity.class); Intent intent = new Intent(this, HelpActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
} }
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
// Handle action buttons // Handle action buttons
@ -193,10 +212,18 @@ public class DrawerActivity extends ActionBarActivity {
private void selectItem(int position) { private void selectItem(int position) {
// update selected item and title, then close the drawer // update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true); mDrawerList.setItemChecked(position, true);
// setTitle(mDrawerTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
// set selected class // set selected class
mSelectedItem = mItemsClass[position]; mSelectedItem = Constants.DrawerItems.ARRAY[position];
// setTitle(mDrawerTitles[position]);
// If drawer isn't locked just close the drawer and
// it will move to the selected item by itself (via drawer toggle listener)
if ( !mIsDrawerLocked ) {
mDrawerLayout.closeDrawer(mDrawerList);
// else move to the selected item yourself
} else {
callIntentForDrawerItem(mSelectedItem);
}
} }
/** /**
@ -229,15 +256,15 @@ public class DrawerActivity extends ActionBarActivity {
} }
private class NavigationDrawerAdapter extends ArrayAdapter<NavItem> { private class NavigationDrawerAdapter extends ArrayAdapter<NavItem> {
Context context; Context mContext;
int layoutResourceId; int mLayoutResourceId;
NavItem data[] = null; NavItem mData[] = null;
public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) { public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) {
super(context, layoutResourceId, data); super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId; this.mLayoutResourceId = layoutResourceId;
this.context = context; this.mContext = context;
this.data = data; this.mData = data;
} }
@Override @Override
@ -246,21 +273,21 @@ public class DrawerActivity extends ActionBarActivity {
NavItemHolder holder = null; NavItemHolder holder = null;
if (row == null) { if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater(); LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false); row = inflater.inflate(mLayoutResourceId, parent, false);
holder = new NavItemHolder(); holder = new NavItemHolder();
holder.img = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon); holder.mImg = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon);
holder.txtTitle = (TextView) row.findViewById(R.id.drawer_item_text); holder.mTxtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
row.setTag(holder); row.setTag(holder);
} else { } else {
holder = (NavItemHolder) row.getTag(); holder = (NavItemHolder) row.getTag();
} }
NavItem item = data[position]; NavItem item = mData[position];
holder.txtTitle.setText(item.title); holder.mTxtTitle.setText(item.title);
holder.img.setIcon(item.icon); holder.mImg.setIcon(item.icon);
return row; return row;
} }
@ -268,8 +295,8 @@ public class DrawerActivity extends ActionBarActivity {
} }
static class NavItemHolder { static class NavItemHolder {
FontAwesomeText img; FontAwesomeText mImg;
TextView txtTitle; TextView mTxtTitle;
} }
} }

View File

@ -23,6 +23,25 @@ import java.util.List;
import java.util.Vector; import java.util.Vector;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
import android.view.*;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
@ -32,6 +51,7 @@ import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
@ -49,31 +69,12 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.beardedhen.androidbootstrap.BootstrapButton;
public class EditKeyActivity extends ActionBarActivity implements EditorListener { public class EditKeyActivity extends ActionBarActivity implements EditorListener {
@ -113,7 +114,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
Vector<String> mUserIds; Vector<String> mUserIds;
Vector<PGPSecretKey> mKeys; Vector<PGPSecretKey> mKeys;
Vector<Integer> mKeysUsages; Vector<Integer> mKeysUsages;
boolean masterCanSign = true; boolean mMasterCanSign = true;
ExportHelper mExportHelper; ExportHelper mExportHelper;
@ -211,9 +212,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data); serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after generating is done in ApgService // Message is received after generating is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER, true, this, getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_HORIZONTAL, true,
new DialogInterface.OnCancelListener() { new DialogInterface.OnCancelListener() {
@Override @Override
@ -227,7 +229,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -283,12 +285,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
} else { } else {
Log.d(Constants.TAG, "uri: " + mDataUri); Log.d(Constants.TAG, "uri: " + mDataUri);
long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment());
// get master key id using row id // get master key id using row id
long masterKeyId = ProviderHelper.getSecretMasterKeyId(this, keyRingRowId); long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
masterCanSign = ProviderHelper.getSecretMasterKeyCanCertify(this, keyRingRowId); mMasterCanSign = ProviderHelper.getMasterKeyCanCertify(this, mDataUri);
finallyEdit(masterKeyId); finallyEdit(masterKeyId);
} }
} }
@ -350,11 +350,16 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
if (needsSaving()) { if (needsSaving()) {
Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show();
} else { } else {
mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
+ "/secexport.asc"); long[] ids = new long[]{masterKeyId};
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC,
null);
return true;
} }
return true; return true;
case R.id.menu_key_edit_delete: { case R.id.menu_key_edit_delete:
long rowId= ProviderHelper.getRowId(this,mDataUri);
Uri convertUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
// Message is received after key is deleted // Message is received after key is deleted
Handler returnHandler = new Handler() { Handler returnHandler = new Handler() {
@Override @Override
@ -363,12 +368,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
finish(); finish();
} }
} }};
}; mExportHelper.deleteKey(convertUri, returnHandler);
mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler);
return true; return true;
}
case R.id.menu_key_edit_save: case R.id.menu_key_edit_save:
saveClicked(); saveClicked();
return true; return true;
@ -407,8 +410,8 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
} }
mCurrentPassphrase = ""; mCurrentPassphrase = "";
buildLayout(false); buildLayout(false);
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
if (!mIsPassPhraseSet) { if (!mIsPassPhraseSet) {
// check "no passphrase" checkbox and remove button // check "no passphrase" checkbox and remove button
@ -466,20 +469,23 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// find views // find views
mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase); mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
// Build layout based on given userIds and keys // Build layout based on given userIds and keys
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container); LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
if(mIsPassPhraseSet){
mChangePassphrase.setText(getString(R.string.btn_change_passphrase));
}
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mUserIdsView.setType(Id.type.user_id); mUserIdsView.setType(Id.type.user_id);
mUserIdsView.setCanEdit(masterCanSign); mUserIdsView.setCanEdit(mMasterCanSign);
mUserIdsView.setUserIds(mUserIds); mUserIdsView.setUserIds(mUserIds);
mUserIdsView.setEditorListener(this); mUserIdsView.setEditorListener(this);
container.addView(mUserIdsView); container.addView(mUserIdsView);
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mKeysView.setType(Id.type.key); mKeysView.setType(Id.type.key);
mKeysView.setCanEdit(masterCanSign); mKeysView.setCanEdit(mMasterCanSign);
mKeysView.setKeys(mKeys, mKeysUsages, newKeys); mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
mKeysView.setEditorListener(this); mKeysView.setEditorListener(this);
container.addView(mKeysView); container.addView(mKeysView);
@ -602,17 +608,16 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// fill values for this action // fill values for this action
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, masterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams); data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after saving is done in ApgService // Message is received after saving is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -640,8 +645,9 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// start service with intent // start service with intent
startService(intent); startService(intent);
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
Toast.makeText(this, getString(R.string.error_message, e.getMessage()), Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
} }
@ -692,11 +698,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
for (int i = 0; i < userIdEditors.getChildCount(); ++i) { for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
String userId; String userId;
try { userId = editor.getValue();
userId = editor.getValue();
} catch (UserIdEditor.InvalidEmailException e) {
throw new PgpGeneralException(e.getMessage());
}
if (editor.isMainUserId()) { if (editor.isMainUserId()) {
userIds.add(0, userId); userIds.add(0, userId);

View File

@ -17,9 +17,22 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.io.File; import android.app.ProgressDialog;
import java.util.Vector; import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.*;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.beardedhen.androidbootstrap.FontAwesomeText;
import com.devspark.appmsg.AppMsg;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
@ -43,27 +56,8 @@ import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Choice;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import android.app.ProgressDialog; import java.io.File;
import android.content.Intent; import java.util.Vector;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
public class EncryptActivity extends DrawerActivity { public class EncryptActivity extends DrawerActivity {
@ -108,6 +102,7 @@ public class EncryptActivity extends DrawerActivity {
private EditText mFilename = null; private EditText mFilename = null;
private CheckBox mDeleteAfter = null; private CheckBox mDeleteAfter = null;
private CheckBox mShareAfter = null;
private BootstrapButton mBrowse = null; private BootstrapButton mBrowse = null;
private String mInputFilename = null; private String mInputFilename = null;
@ -602,11 +597,11 @@ public class EncryptActivity extends DrawerActivity {
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after encrypting is done in ApgService // Message is received after encrypting is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_encrypting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -650,6 +645,15 @@ public class EncryptActivity extends DrawerActivity {
.newInstance(mInputFilename); .newInstance(mInputFilename);
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
} }
if (mShareAfter.isChecked()) {
// Share encrypted file
Intent sendFileIntent = new Intent(Intent.ACTION_SEND);
sendFileIntent.setType("*/*");
sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename));
startActivity(Intent.createChooser(sendFileIntent,
getString(R.string.title_send_file)));
}
break; break;
default: default:
@ -659,8 +663,6 @@ public class EncryptActivity extends DrawerActivity {
} }
} }
} }
;
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -785,6 +787,11 @@ public class EncryptActivity extends DrawerActivity {
} }
}); });
mFileCompression = (Spinner) findViewById(R.id.fileCompression); mFileCompression = (Spinner) findViewById(R.id.fileCompression);
Choice[] choices = new Choice[]{ Choice[] choices = new Choice[]{
new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
@ -794,7 +801,7 @@ public class EncryptActivity extends DrawerActivity {
new Choice(Id.choice.compression.zlib, "ZLIB (" new Choice(Id.choice.compression.zlib, "ZLIB ("
+ getString(R.string.compression_fast) + ")"), + getString(R.string.compression_fast) + ")"),
new Choice(Id.choice.compression.bzip2, "BZIP2 (" new Choice(Id.choice.compression.bzip2, "BZIP2 ("
+ getString(R.string.compression_very_slow) + ")"),}; + getString(R.string.compression_very_slow) + ")"), };
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(this, ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(this,
android.R.layout.simple_spinner_item, choices); android.R.layout.simple_spinner_item, choices);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@ -809,6 +816,7 @@ public class EncryptActivity extends DrawerActivity {
} }
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption);
mShareAfter = (CheckBox) findViewById(R.id.shareAfterEncryption);
mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour); mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour);
mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour()); mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour());
@ -947,8 +955,8 @@ public class EncryptActivity extends DrawerActivity {
case Id.request.secret_keys: { case Id.request.secret_keys: {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras(); Uri uri_master_key = data.getData();
mSecretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID); mSecretKeyId = Long.valueOf(uri_master_key.getLastPathSegment());
} else { } else {
mSecretKeyId = Id.key.none; mSecretKeyId = Id.key.none;
} }

View File

@ -17,11 +17,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.htmltextview.HtmlTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@ -31,6 +26,10 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.htmltextview.HtmlTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class HelpAboutFragment extends Fragment { public class HelpAboutFragment extends Fragment {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de> * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,24 +17,16 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import java.util.ArrayList;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.widget.TextView; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
public class HelpActivity extends ActionBarActivity { public class HelpActivity extends ActionBarActivity {
public static final String EXTRA_SELECTED_TAB = "selectedTab"; public static final String EXTRA_SELECTED_TAB = "selected_tab";
ViewPager mViewPager; ViewPager mViewPager;
TabsAdapter mTabsAdapter; TabsAdapter mTabsAdapter;
@ -64,19 +56,24 @@ public class HelpActivity extends ActionBarActivity {
Bundle startBundle = new Bundle(); Bundle startBundle = new Bundle();
startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start); startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)),
HelpHtmlFragment.class, startBundle, (selectedTab == 0 ? true : false)); HelpHtmlFragment.class, startBundle, (selectedTab == 0));
Bundle faqBundle = new Bundle();
faqBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_faq);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_faq)),
HelpHtmlFragment.class, faqBundle, (selectedTab == 1));
Bundle nfcBundle = new Bundle(); Bundle nfcBundle = new Bundle();
nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)),
HelpHtmlFragment.class, nfcBundle, (selectedTab == 1 ? true : false)); HelpHtmlFragment.class, nfcBundle, (selectedTab == 2));
Bundle changelogBundle = new Bundle(); Bundle changelogBundle = new Bundle();
changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)),
HelpHtmlFragment.class, changelogBundle, (selectedTab == 2 ? true : false)); HelpHtmlFragment.class, changelogBundle, (selectedTab == 3));
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)),
HelpAboutFragment.class, null, (selectedTab == 3 ? true : false)); HelpAboutFragment.class, null, (selectedTab == 4));
} }
} }

View File

@ -17,8 +17,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.htmltextview.HtmlTextView;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@ -27,11 +25,12 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ScrollView; import android.widget.ScrollView;
import org.sufficientlysecure.htmltextview.HtmlTextView;
public class HelpHtmlFragment extends Fragment { public class HelpHtmlFragment extends Fragment {
private Activity mActivity; private Activity mActivity;
private int htmlFile; private int mHtmlFile;
public static final String ARG_HTML_FILE = "htmlFile"; public static final String ARG_HTML_FILE = "htmlFile";
@ -53,7 +52,7 @@ public class HelpHtmlFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mActivity = getActivity(); mActivity = getActivity();
htmlFile = getArguments().getInt(ARG_HTML_FILE); mHtmlFile = getArguments().getInt(ARG_HTML_FILE);
ScrollView scroller = new ScrollView(mActivity); ScrollView scroller = new ScrollView(mActivity);
HtmlTextView text = new HtmlTextView(mActivity); HtmlTextView text = new HtmlTextView(mActivity);
@ -66,7 +65,7 @@ public class HelpHtmlFragment extends Fragment {
scroller.addView(text); scroller.addView(text);
// load html from raw resource (Parsing handled by HtmlTextView library) // load html from raw resource (Parsing handled by HtmlTextView library)
text.setHtmlFromRawResource(getActivity(), htmlFile); text.setHtmlFromRawResource(getActivity(), mHtmlFile);
// no flickering when clicking textview for Android < 4 // no flickering when clicking textview for Android < 4
text.setTextColor(getResources().getColor(android.R.color.black)); text.setTextColor(getResources().getColor(android.R.color.black));

View File

@ -34,10 +34,8 @@ import android.support.v7.app.ActionBar;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg; import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
@ -161,7 +159,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
} else if (extras.containsKey(EXTRA_KEY_ID)) { } else if (extras.containsKey(EXTRA_KEY_ID)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0); long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
if (keyId != 0) { if (keyId != 0) {
query = "0x" + PgpKeyHelper.convertKeyToHex(keyId); query = PgpKeyHelper.convertKeyIdToHex(keyId);
} }
} else if (extras.containsKey(EXTRA_FINGERPRINT)) { } else if (extras.containsKey(EXTRA_FINGERPRINT)) {
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT); String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
@ -169,7 +167,8 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
query = "0x" + fingerprint; query = "0x" + fingerprint;
} }
} else { } else {
Log.e(Constants.TAG, "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or 'fingerprint' extra!"); Log.e(Constants.TAG,
"IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or 'fingerprint' extra!");
return; return;
} }
@ -338,7 +337,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
// } else { // } else {
// status.putString( // status.putString(
// EXTRA_ERROR, // EXTRA_ERROR,
// "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key."); // "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
// } // }
// } // }
// } catch (QueryException e) { // } catch (QueryException e) {
@ -360,51 +359,54 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
// } // }
// Message is received after importing is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get returned data bundle
Bundle returnData = message.getData();
int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
int updated = returnData
.getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
String toastMessage;
if (added > 0 && updated > 0) {
String addedStr = getResources().getQuantityString(
R.plurals.keys_added_and_updated_1, added, added);
String updatedStr = getResources().getQuantityString(
R.plurals.keys_added_and_updated_2, updated, updated);
toastMessage = addedStr + updatedStr;
} else if (added > 0) {
toastMessage = getResources().getQuantityString(R.plurals.keys_added,
added, added);
} else if (updated > 0) {
toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
updated, updated);
} else {
toastMessage = getString(R.string.no_keys_added_or_updated);
}
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
.show();
if (bad > 0) {
BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad);
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
}
}
}
};
/** /**
* Import keys with mImportData * Import keys with mImportData
*/ */
public void importKeys() { public void importKeys() {
// Message is received after importing is done in KeychainIntentService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get returned data bundle
Bundle returnData = message.getData();
int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
int updated = returnData
.getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
String toastMessage;
if (added > 0 && updated > 0) {
String addedStr = getResources().getQuantityString(
R.plurals.keys_added_and_updated_1, added, added);
String updatedStr = getResources().getQuantityString(
R.plurals.keys_added_and_updated_2, updated, updated);
toastMessage = addedStr + updatedStr;
} else if (added > 0) {
toastMessage = getResources().getQuantityString(R.plurals.keys_added,
added, added);
} else if (updated > 0) {
toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
updated, updated);
} else {
toastMessage = getString(R.string.no_keys_added_or_updated);
}
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
.show();
if (bad > 0) {
BadImportKeyDialogFragment badImportKeyDialogFragment =
BadImportKeyDialogFragment.newInstance(bad);
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
}
}
}
};
if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) { if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) {
Log.d(Constants.TAG, "importKeys started"); Log.d(Constants.TAG, "importKeys started");

View File

@ -17,17 +17,15 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
public class ImportKeysClipboardFragment extends Fragment { public class ImportKeysClipboardFragment extends Fragment {
@ -60,8 +58,9 @@ public class ImportKeysClipboardFragment extends Fragment {
public void onClick(View v) { public void onClick(View v) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
String sendText = ""; String sendText = "";
if (clipboardText != null) if (clipboardText != null) {
sendText = clipboardText.toString(); sendText = clipboardText.toString();
}
mImportActivity.loadCallback(sendText.getBytes(), null, null, null); mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
} }
}); });

View File

@ -17,11 +17,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -29,8 +24,11 @@ import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.FileHelper;
public class ImportKeysFileFragment extends Fragment { public class ImportKeysFileFragment extends Fragment {
private ImportKeysActivity mImportActivity; private ImportKeysActivity mImportActivity;
@ -62,7 +60,7 @@ public class ImportKeysFileFragment extends Fragment {
// open .asc or .gpg files // open .asc or .gpg files
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
// or gpg types! // or gpg types!
FileHelper.openFile(ImportKeysFileFragment.this, Constants.path.APP_DIR + "/", FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/",
"*/*", Id.request.filename); "*/*", Id.request.filename);
} }
}); });

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