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
[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:
# Install base Android SDK
- 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
- tar xzf android-sdk_r22.3-linux.tgz
- 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
* hotfix for crash when upgrading from old versions

View File

@ -13,7 +13,8 @@ apply plugin: 'android'
dependencies {
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 {

View File

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

View File

@ -46,6 +46,7 @@
android:scrollHorizontally="true"
android:scrollbars="vertical"
android:text="message"
android:hint="cleartext message"
android:textAppearance="@android:style/TextAppearance.Small" />
</ScrollView>
@ -66,6 +67,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="ciphertext"
android:hint="ciphertext"
android:textAppearance="@android:style/TextAppearance.Small" />
</ScrollView>
@ -104,5 +106,18 @@
android:layout_height="wrap_content"
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>
</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 {
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 */
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
// request ASCII Armor for output
// 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.ListAdapter;
import android.widget.TextView;
import org.sufficientlysecure.keychain.api.R;
import org.openintents.openpgp.R;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,2 +1,3 @@
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 {
compile 'com.android.support:support-v4: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:StickyListHeaders:library')
compile project(':libraries:AndroidBootstrap')
@ -56,5 +57,7 @@ android {
// Do not abort build if lint finds errors
lintOptions {
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"
package="org.sufficientlysecure.keychain"
android:installLocation="auto"
android:versionCode="23103"
android:versionName="2.3.1 beta3">
android:versionCode="24000"
android:versionName="2.4">
<!--
General remarks
@ -50,6 +50,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<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! -->
<application
@ -60,7 +61,7 @@
android:theme="@style/KeychainTheme"
android:label="@string/app_name">
<activity
android:name=".ui.KeyListPublicActivity"
android:name=".ui.KeyListActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/app_name"
android:launchMode="singleTop">
@ -70,12 +71,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.KeyListSecretActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_manage_secret_keys"
android:launchMode="singleTop">
</activity>
<activity
android:name=".ui.EditKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -85,32 +80,30 @@
android:name=".ui.ViewKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_key_details"
android:parentActivityName=".ui.KeyListPublicActivity">
android:parentActivityName=".ui.KeyListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.KeyListPublicActivity" />
android:value=".ui.KeyListActivity" />
</activity>
<activity
android:name=".ui.ViewKeyActivityJB"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_key_details"
android:parentActivityName=".ui.KeyListPublicActivity">
android:parentActivityName=".ui.KeyListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.KeyListPublicActivity" />
android:value=".ui.KeyListActivity" />
</activity>
<activity
android:name=".ui.SelectPublicKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_recipients"
android:launchMode="singleTop">
</activity>
android:launchMode="singleTop"></activity>
<activity
android:name=".ui.SelectSecretKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_secret_key"
android:launchMode="singleTop">
</activity>
android:launchMode="singleTop"></activity>
<activity
android:name=".ui.EncryptActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -379,44 +372,49 @@
android:exported="false"
android:process=":passphrase_cache" />
<service
android:name="org.sufficientlysecure.keychain.service.KeychainIntentService"
android:name=".service.KeychainIntentService"
android:exported="false" />
<provider
android:name="org.sufficientlysecure.keychain.provider.KeychainProvider"
android:name=".provider.KeychainProvider"
android:authorities="org.sufficientlysecure.keychain.provider"
android:exported="false" />
<!-- Internal classes of the remote APIs (not exported) -->
<activity
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
android:name=".remote.ui.RemoteServiceActivity"
android:exported="false"
android:label="@string/app_name" />
<!--android:launchMode="singleTop"-->
<!--android:process=":remote_api"-->
android:label="@string/app_name"
android:launchMode="singleTop"
android:process=":remote_api" />
<activity
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity"
android:name=".remote.ui.AppsListActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:exported="false"
android:label="@string/title_api_registered_apps" />
<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:exported="false" />
<!-- OpenPGP Remote API -->
<service
android:name="org.sufficientlysecure.keychain.service.remote.OpenPgpService"
android:name=".remote.OpenPgpService"
android:enabled="true"
android:exported="true"
android:process=":remote_api">
<intent-filter>
<action android:name="org.openintents.openpgp.IOpenPgpService" />
</intent-filter>
<meta-data
android:name="api_version"
android:value="1" />
</service>
<!-- Extended Remote API -->

View File

@ -16,10 +16,15 @@
package org.sufficientlysecure.keychain;
import org.spongycastle.jce.provider.BouncyCastleProvider;
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 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 class path {
public static final class Path {
public static final String APP_DIR = Environment.getExternalStorageDirectory()
+ "/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_HASH_ALGORITHM = "defaultHashAlgorithm";
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 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 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 user_id = 0x21070003;
public static final int key = 0x21070004;
public static final int public_secret_key = 0x21070005;
}
public static final class choice {

View File

@ -64,7 +64,7 @@ public class KeychainApplication extends Application {
// Create APG directory on sdcard if not existing
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()) {
// ignore this for now, it's not crucial
// 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
*
* @param context
* @param text
*/
public static CharSequence getClipboardText(Context context) {
Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);

View File

@ -37,7 +37,8 @@ import android.os.Handler;
* </code>
*/
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 =
((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ? new SDKLevel17Impl()
: new SDKLevelPriorLevel17Impl());
private static final int RUNNABLE_DELAY = 300;

View File

@ -17,18 +17,17 @@
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.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
public class ActionBarHelper {
@ -38,7 +37,6 @@ public class ActionBarHelper {
* @param activity
*/
public static void setBackButton(ActionBarActivity activity) {
// set actionbar without home button if called from another app
final ActionBar actionBar = activity.getSupportActionBar();
Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
+ activity.getCallingPackage());
@ -56,28 +54,33 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done/Cancel activities
*
* @param actionBar
* @param doneText
* @param doneOnClickListener
* @param cancelText
* @param cancelOnClickListener
* @param firstText
* @param firstDrawableId
* @param firstOnClickListener
* @param secondText
* @param secondDrawableId
* @param secondOnClickListener
*/
public static void setDoneCancelView(ActionBar actionBar, int doneText,
OnClickListener doneOnClickListener, int cancelText,
OnClickListener cancelOnClickListener) {
public static void setTwoButtonView(ActionBar actionBar,
int firstText, int firstDrawableId, OnClickListener firstOnClickListener,
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()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater.inflate(
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(
doneOnClickListener);
((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text))
.setText(cancelText);
firstOnClickListener);
TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
secondTextView.setText(secondText);
secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
cancelOnClickListener);
secondOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false);
@ -91,20 +94,22 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done activities
*
* @param actionBar
* @param doneText
* @param doneOnClickListener
* @param firstText
* @param firstOnClickListener
*/
public static void setDoneView(ActionBar actionBar, int doneText,
OnClickListener doneOnClickListener) {
public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
OnClickListener firstOnClickListener) {
// Inflate a "Done" custom action bar view to serve as the "Up" affordance.
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater
.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(
doneOnClickListener);
firstOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false);
@ -112,5 +117,4 @@ public class ActionBarHelper {
actionBar.setDisplayShowCustomEnabled(true);
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;
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.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -36,35 +26,50 @@ import android.os.Message;
import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
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 {
protected FileDialogFragment mFileDialog;
protected String mExportFilename;
ActionBarActivity activity;
ActionBarActivity mActivity;
public ExportHelper(ActionBarActivity activity) {
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());
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(deleteHandler);
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
*/
public void showExportKeysDialog(final Uri dataUri, final int keyType,
final String exportFilename) {
public void showExportKeysDialog(final long[] masterKeyIds, final int keyType,
final String exportFilename, final String checkboxString) {
mExportFilename = exportFilename;
// Message is received after file is selected
@ -73,9 +78,14 @@ public class ExportHelper {
public void handleMessage(Message message) {
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
Bundle data = message.getData();
int type = keyType;
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() {
public void run() {
String title = null;
if (dataUri == null) {
if (masterKeyIds == null) {
// export all keys
title = activity.getString(R.string.title_export_keys);
title = mActivity.getString(R.string.title_export_keys);
} else {
// 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;
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);
}
String message = mActivity.getString(R.string.specify_file_to_export_to);
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
*/
public void exportKeys(Uri dataUri, int keyType) {
public void exportKeys(long[] masterKeyIds, int keyType) {
Log.d(Constants.TAG, "exportKeys started");
// 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);
@ -126,22 +131,27 @@ public class ExportHelper {
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType);
if (dataUri == null) {
if (masterKeyIds == null) {
data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
} else {
// TODO: put data uri into service???
long keyRingMasterKeyId = ProviderHelper.getMasterKeyId(activity, dataUri);
data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId);
data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);
}
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after exporting is done in ApgService
KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
// Message is received after exporting is done in KeychainIntentService
KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity,
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) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -151,16 +161,16 @@ public class ExportHelper {
int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT);
String toastMessage;
if (exported == 1) {
toastMessage = activity.getString(R.string.key_exported);
toastMessage = mActivity.getString(R.string.key_exported);
} else if (exported > 0) {
toastMessage = activity.getString(R.string.keys_exported, exported);
toastMessage = mActivity.getString(R.string.keys_exported, exported);
} 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
@ -168,10 +178,10 @@ public class ExportHelper {
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
exportHandler.showProgressDialog(activity);
exportHandler.showProgressDialog(mActivity);
// start service with intent
activity.startService(intent);
mActivity.startService(intent);
}
}

View File

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

View File

@ -17,15 +17,16 @@
package org.sufficientlysecure.keychain.helper;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Set;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StrikethroughSpan;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;
import android.os.Bundle;
import java.util.Iterator;
import java.util.Set;
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;
import android.content.Context;
import android.content.SharedPreferences;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Vector;
/**
@ -38,8 +37,8 @@ public class Preferences {
return getPreferences(context, false);
}
public static synchronized Preferences getPreferences(Context context, boolean force_new) {
if (mPreferences == null || force_new) {
public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
if (mPreferences == null || forceNew) {
mPreferences = new Preferences(context);
}
return mPreferences;
@ -50,17 +49,17 @@ public class Preferences {
}
public String getLanguage() {
return mSharedPreferences.getString(Constants.pref.LANGUAGE, "");
return mSharedPreferences.getString(Constants.Pref.LANGUAGE, "");
}
public void setLanguage(String value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Constants.pref.LANGUAGE, value);
editor.putString(Constants.Pref.LANGUAGE, value);
editor.commit();
}
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
// supported
if (ttl == 0) {
@ -71,81 +70,81 @@ public class Preferences {
public void setPassPhraseCacheTtl(int value) {
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();
}
public int getDefaultEncryptionAlgorithm() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM,
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM,
PGPEncryptedData.AES_256);
}
public void setDefaultEncryptionAlgorithm(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, value);
editor.putInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, value);
editor.commit();
}
public int getDefaultHashAlgorithm() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_HASH_ALGORITHM,
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_HASH_ALGORITHM,
HashAlgorithmTags.SHA512);
}
public void setDefaultHashAlgorithm(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_HASH_ALGORITHM, value);
editor.putInt(Constants.Pref.DEFAULT_HASH_ALGORITHM, value);
editor.commit();
}
public int getDefaultMessageCompression() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION,
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION,
Id.choice.compression.zlib);
}
public void setDefaultMessageCompression(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, value);
editor.putInt(Constants.Pref.DEFAULT_MESSAGE_COMPRESSION, value);
editor.commit();
}
public int getDefaultFileCompression() {
return mSharedPreferences.getInt(Constants.pref.DEFAULT_FILE_COMPRESSION,
return mSharedPreferences.getInt(Constants.Pref.DEFAULT_FILE_COMPRESSION,
Id.choice.compression.none);
}
public void setDefaultFileCompression(int value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt(Constants.pref.DEFAULT_FILE_COMPRESSION, value);
editor.putInt(Constants.Pref.DEFAULT_FILE_COMPRESSION, value);
editor.commit();
}
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) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, value);
editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, value);
editor.commit();
}
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) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.pref.FORCE_V3_SIGNATURES, value);
editor.putBoolean(Constants.Pref.FORCE_V3_SIGNATURES, value);
editor.commit();
}
public String[] getKeyServers() {
String rawData = mSharedPreferences.getString(Constants.pref.KEY_SERVERS,
Constants.defaults.KEY_SERVERS);
String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
Constants.Defaults.KEY_SERVERS);
Vector<String> servers = new Vector<String>();
String chunks[] = rawData.split(",");
for (int i = 0; i < chunks.length; ++i) {
String tmp = chunks[i].trim();
for (String c : chunks) {
String tmp = c.trim();
if (tmp.length() > 0) {
servers.add(tmp);
}
@ -156,8 +155,8 @@ public class Preferences {
public void setKeyServers(String[] value) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
String rawData = "";
for (int i = 0; i < value.length; ++i) {
String tmp = value[i].trim();
for (String v : value) {
String tmp = v.trim();
if (tmp.length() == 0) {
continue;
}
@ -166,7 +165,7 @@ public class Preferences {
}
rawData += tmp;
}
editor.putString(Constants.pref.KEY_SERVERS, rawData);
editor.putString(Constants.Pref.KEY_SERVERS, rawData);
editor.commit();
}
}

View File

@ -17,11 +17,6 @@
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.PGPObjectFactory;
import org.spongycastle.openpgp.PGPSecretKey;
@ -29,6 +24,11 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.Constants;
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 {
@ -90,10 +90,10 @@ public class PgpConversionHelper {
/**
* Convert from byte[] to PGPSecretKey
*
* <p/>
* Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
*
* @param keysBytes
* @param keyBytes
* @return
*/
public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
@ -142,7 +142,7 @@ public class PgpConversionHelper {
/**
* Convert from PGPSecretKey to byte[]
*
* @param keysBytes
* @param key
* @return
*/
public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
@ -158,7 +158,7 @@ public class PgpConversionHelper {
/**
* Convert from PGPSecretKeyRing to byte[]
*
* @param keysBytes
* @param keyRing
* @return
*/
public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {

View File

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

View File

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

View File

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

View File

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

View File

@ -17,28 +17,27 @@
package org.sufficientlysecure.keychain.pgp;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import org.spongycastle.bcpg.sig.KeyFlags;
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.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.*;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.IterableIterator;
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 {
@ -492,31 +491,6 @@ public class PgpKeyHelper {
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) {
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
// if it is no public key get it from your own keys...
@ -529,45 +503,140 @@ public class PgpKeyHelper {
key = secretKey.getPublicKey();
}
return convertFingerprintToHex(key.getFingerprint(), true);
}
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;
return convertFingerprintToHex(key.getFingerprint());
}
/**
* 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
* @return
*/
public static String convertKeyToHex(long keyId) {
return convertKeyIdToHex(keyId >> 32) + convertKeyIdToHex(keyId);
public static String convertKeyIdToHex(long 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) {
int len = data.length();
String s2 = data.substring(len - 8);
String s1 = data.substring(0, len - 8);
return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16);
public static String convertKeyIdToHexShort(long keyId) {
return "0x" + convertKeyIdToHex32bit(keyId);
}
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;
}
/**
@ -598,7 +667,6 @@ public class PgpKeyHelper {
result[0] = matcher.group(1);
result[1] = matcher.group(3);
result[2] = matcher.group(2);
return result;
}
return result;

View File

@ -30,35 +30,19 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.TimeZone;
import android.content.Context;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPEncryptedData;
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.*;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
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.spongycastle.openpgp.operator.jcajce.*;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
@ -70,6 +54,20 @@ import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.content.Context;
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 {
private final Context mContext;
@ -104,7 +102,8 @@ public class PgpKeyOperation {
}
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
boolean isMasterKey)
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
PgpGeneralException, InvalidAlgorithmParameterException {
if (keySize < 512) {
@ -170,7 +169,6 @@ public class PgpKeyOperation {
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor);
}
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
@ -280,7 +278,7 @@ public class PgpKeyOperation {
updateProgress(R.string.progress_adding_sub_keys, 40, 100);
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);
PGPPublicKey subPublicKey = subKey.getPublicKey();
@ -642,19 +640,43 @@ public class PgpKeyOperation {
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
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, pKR);
updateProgress(R.string.progress_done, 100, 100);
}
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase)
throws PgpGeneralException, PGPException, SignatureException {
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
PGPException, SignatureException {
if (passphrase == null) {
throw new PgpGeneralException("Unable to obtain passphrase");
} else {
PGPPublicKeyRing pubring = ProviderHelper
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
// create a signatureGenerator from the supplied masterKeyId and passphrase
PGPSignatureGenerator signatureGenerator; {
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
if (certificationKey == null) {
@ -674,18 +696,24 @@ public class PgpKeyOperation {
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
contentSignerBuilder);
signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey);
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);
}
PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId),
signatureGenerator.generate());
// fetch public key ring, add the certification and return it
PGPPublicKeyRing pubring = ProviderHelper
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId);
for(String userId : new IterableIterator<String>(userIds.iterator())) {
PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey);
signedKey = PGPPublicKey.addCertification(signedKey, userId, sig);
}
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
return pubring;

View File

@ -18,28 +18,11 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
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.*;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
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.spongycastle.openpgp.operator.jcajce.*;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
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.ProgressDialogUpdater;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.*;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
@ -63,110 +42,110 @@ import java.util.Date;
* This class uses a Builder pattern!
*/
public class PgpSignEncrypt {
private Context context;
private InputData data;
private OutputStream outStream;
private Context mContext;
private InputData mData;
private OutputStream mOutStream;
private ProgressDialogUpdater progress;
private boolean enableAsciiArmorOutput;
private int compressionId;
private long[] encryptionKeyIds;
private String encryptionPassphrase;
private int symmetricEncryptionAlgorithm;
private long signatureKeyId;
private int signatureHashAlgorithm;
private boolean signatureForceV3;
private String signaturePassphrase;
private ProgressDialogUpdater mProgress;
private boolean mEnableAsciiArmorOutput;
private int mCompressionId;
private long[] mEncryptionKeyIds;
private String mEncryptionPassphrase;
private int mSymmetricEncryptionAlgorithm;
private long mSignatureKeyId;
private int mSignatureHashAlgorithm;
private boolean mSignatureForceV3;
private String mSignaturePassphrase;
private PgpSignEncrypt(Builder builder) {
// private Constructor can only be called from Builder
this.context = builder.context;
this.data = builder.data;
this.outStream = builder.outStream;
this.mContext = builder.mContext;
this.mData = builder.mData;
this.mOutStream = builder.mOutStream;
this.progress = builder.progress;
this.enableAsciiArmorOutput = builder.enableAsciiArmorOutput;
this.compressionId = builder.compressionId;
this.encryptionKeyIds = builder.encryptionKeyIds;
this.encryptionPassphrase = builder.encryptionPassphrase;
this.symmetricEncryptionAlgorithm = builder.symmetricEncryptionAlgorithm;
this.signatureKeyId = builder.signatureKeyId;
this.signatureHashAlgorithm = builder.signatureHashAlgorithm;
this.signatureForceV3 = builder.signatureForceV3;
this.signaturePassphrase = builder.signaturePassphrase;
this.mProgress = builder.mProgress;
this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
this.mCompressionId = builder.mCompressionId;
this.mEncryptionKeyIds = builder.mEncryptionKeyIds;
this.mEncryptionPassphrase = builder.mEncryptionPassphrase;
this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
this.mSignatureKeyId = builder.mSignatureKeyId;
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
this.mSignatureForceV3 = builder.mSignatureForceV3;
this.mSignaturePassphrase = builder.mSignaturePassphrase;
}
public static class Builder {
// mandatory parameter
private Context context;
private InputData data;
private OutputStream outStream;
private Context mContext;
private InputData mData;
private OutputStream mOutStream;
// optional
private ProgressDialogUpdater progress = null;
private boolean enableAsciiArmorOutput = false;
private int compressionId = Id.choice.compression.none;
private long[] encryptionKeyIds = new long[0];
private String encryptionPassphrase = null;
private int symmetricEncryptionAlgorithm = 0;
private long signatureKeyId = Id.key.none;
private int signatureHashAlgorithm = 0;
private boolean signatureForceV3 = false;
private String signaturePassphrase = null;
private ProgressDialogUpdater mProgress = null;
private boolean mEnableAsciiArmorOutput = false;
private int mCompressionId = Id.choice.compression.none;
private long[] mEncryptionKeyIds = new long[0];
private String mEncryptionPassphrase = null;
private int mSymmetricEncryptionAlgorithm = 0;
private long mSignatureKeyId = Id.key.none;
private int mSignatureHashAlgorithm = 0;
private boolean mSignatureForceV3 = false;
private String mSignaturePassphrase = null;
public Builder(Context context, InputData data, OutputStream outStream) {
this.context = context;
this.data = data;
this.outStream = outStream;
this.mContext = context;
this.mData = data;
this.mOutStream = outStream;
}
public Builder progress(ProgressDialogUpdater progress) {
this.progress = progress;
this.mProgress = progress;
return this;
}
public Builder enableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
this.enableAsciiArmorOutput = enableAsciiArmorOutput;
this.mEnableAsciiArmorOutput = enableAsciiArmorOutput;
return this;
}
public Builder compressionId(int compressionId) {
this.compressionId = compressionId;
this.mCompressionId = compressionId;
return this;
}
public Builder encryptionKeyIds(long[] encryptionKeyIds) {
this.encryptionKeyIds = encryptionKeyIds;
this.mEncryptionKeyIds = encryptionKeyIds;
return this;
}
public Builder encryptionPassphrase(String encryptionPassphrase) {
this.encryptionPassphrase = encryptionPassphrase;
this.mEncryptionPassphrase = encryptionPassphrase;
return this;
}
public Builder symmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
this.symmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
this.mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
return this;
}
public Builder signatureKeyId(long signatureKeyId) {
this.signatureKeyId = signatureKeyId;
this.mSignatureKeyId = signatureKeyId;
return this;
}
public Builder signatureHashAlgorithm(int signatureHashAlgorithm) {
this.signatureHashAlgorithm = signatureHashAlgorithm;
this.mSignatureHashAlgorithm = signatureHashAlgorithm;
return this;
}
public Builder signatureForceV3(boolean signatureForceV3) {
this.signatureForceV3 = signatureForceV3;
this.mSignatureForceV3 = signatureForceV3;
return this;
}
public Builder signaturePassphrase(String signaturePassphrase) {
this.signaturePassphrase = signaturePassphrase;
this.mSignaturePassphrase = signaturePassphrase;
return this;
}
@ -176,14 +155,14 @@ public class PgpSignEncrypt {
}
public void updateProgress(int message, int current, int total) {
if (progress != null) {
progress.setProgress(message, current, total);
if (mProgress != null) {
mProgress.setProgress(message, current, total);
}
}
public void updateProgress(int current, int total) {
if (progress != null) {
progress.setProgress(current, total);
if (mProgress != null) {
mProgress.setProgress(current, total);
}
}
@ -201,17 +180,17 @@ public class PgpSignEncrypt {
throws IOException, PgpGeneralException, PGPException, NoSuchProviderException,
NoSuchAlgorithmException, SignatureException {
boolean enableSignature = signatureKeyId != Id.key.none;
boolean enableEncryption = (encryptionKeyIds.length != 0 || encryptionPassphrase != null);
boolean enableCompression = (enableEncryption && compressionId != Id.choice.compression.none);
boolean enableSignature = mSignatureKeyId != Id.key.none;
boolean enableEncryption = (mEncryptionKeyIds.length != 0 || mEncryptionPassphrase != null);
boolean enableCompression = (enableEncryption && mCompressionId != Id.choice.compression.none);
Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ "\nenableEncryption:" + enableEncryption
+ "\nenableCompression:" + enableCompression
+ "\nenableAsciiArmorOutput:" + enableAsciiArmorOutput);
+ "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput);
int signatureType;
if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
if (mEnableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
// for sign-only ascii text
signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
} else {
@ -220,12 +199,12 @@ public class PgpSignEncrypt {
ArmoredOutputStream armorOut = null;
OutputStream out;
if (enableAsciiArmorOutput) {
armorOut = new ArmoredOutputStream(outStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(context));
if (mEnableAsciiArmorOutput) {
armorOut = new ArmoredOutputStream(mOutStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
out = armorOut;
} else {
out = outStream;
out = mOutStream;
}
/* Get keys for signature generation for later usage */
@ -233,25 +212,25 @@ public class PgpSignEncrypt {
PGPSecretKeyRing signingKeyRing = null;
PGPPrivateKey signaturePrivateKey = null;
if (enableSignature) {
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId);
signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId);
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
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));
mContext.getString(R.string.error_no_signature_passphrase));
}
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
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);
if (signaturePrivateKey == null) {
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);
@ -261,23 +240,23 @@ public class PgpSignEncrypt {
if (enableEncryption) {
// has Integrity packet enabled!
JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(symmetricEncryptionAlgorithm)
new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
.setWithIntegrityPacket(true);
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
if (encryptionKeyIds.length == 0) {
if (mEncryptionKeyIds.length == 0) {
// Symmetric encryption
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
new JcePBEKeyEncryptionMethodGenerator(encryptionPassphrase.toCharArray());
new JcePBEKeyEncryptionMethodGenerator(mEncryptionPassphrase.toCharArray());
cPk.addMethod(symmetricEncryptionGenerator);
} else {
// Asymmetric encryption
for (long id : encryptionKeyIds) {
PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(context, id);
for (long id : mEncryptionKeyIds) {
PGPPublicKey key = PgpKeyHelper.getEncryptPublicKey(mContext, id);
if (key != null) {
JcePublicKeyKeyEncryptionMethodGenerator pubKeyEncryptionGenerator =
new JcePublicKeyKeyEncryptionMethodGenerator(key);
@ -295,10 +274,10 @@ public class PgpSignEncrypt {
// content signer based on signing key algorithm and chosen hash algorithm
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
signingKey.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
signingKey.getPublicKey().getAlgorithm(), mSignatureHashAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
signatureV3Generator.init(signatureType, signaturePrivateKey);
} else {
@ -322,14 +301,14 @@ public class PgpSignEncrypt {
encryptionOut = cPk.open(out, new byte[1 << 16]);
if (enableCompression) {
compressGen = new PGPCompressedDataGenerator(compressionId);
compressGen = new PGPCompressedDataGenerator(mCompressionId);
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
} else {
bcpgOut = new BCPGOutputStream(encryptionOut);
}
if (enableSignature) {
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut);
} else {
signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
@ -345,13 +324,13 @@ public class PgpSignEncrypt {
long progress = 0;
int n;
byte[] buffer = new byte[1 << 16];
InputStream in = data.getInputStream();
InputStream in = mData.getInputStream();
while ((n = in.read(buffer)) > 0) {
pOut.write(buffer, 0, n);
// update signature buffer if signature is requested
if (enableSignature) {
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator.update(buffer, 0, n);
} else {
signatureGenerator.update(buffer, 0, n);
@ -359,26 +338,26 @@ public class PgpSignEncrypt {
}
progress += n;
if (data.getSize() != 0) {
updateProgress((int) (20 + (95 - 20) * progress / data.getSize()), 100);
if (mData.getSize() != 0) {
updateProgress((int) (20 + (95 - 20) * progress / mData.getSize()), 100);
}
}
literalGen.close();
} else if (enableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
} else if (mEnableAsciiArmorOutput && enableSignature && !enableEncryption && !enableCompression) {
/* sign-only of ascii text */
updateProgress(R.string.progress_signing, 40, 100);
// 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 byte[] newline = "\r\n".getBytes("UTF-8");
if (signatureForceV3) {
if (mSignatureForceV3) {
processLine(reader.readLine(), armorOut, signatureV3Generator);
} else {
processLine(reader.readLine(), armorOut, signatureGenerator);
@ -395,7 +374,7 @@ public class PgpSignEncrypt {
armorOut.write(newline);
// update signature buffer with input line
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator.update(newline);
processLine(line, armorOut, signatureV3Generator);
} else {
@ -415,7 +394,7 @@ public class PgpSignEncrypt {
if (enableSignature) {
updateProgress(R.string.progress_generating_signature, 95, 100);
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator.generate().encode(pOut);
} else {
signatureGenerator.generate().encode(pOut);
@ -432,12 +411,12 @@ public class PgpSignEncrypt {
encryptionOut.close();
}
if (enableAsciiArmorOutput) {
if (mEnableAsciiArmorOutput) {
armorOut.close();
}
out.close();
outStream.close();
mOutStream.close();
updateProgress(R.string.progress_done, 100, 100);
}
@ -449,35 +428,36 @@ public class PgpSignEncrypt {
SignatureException {
OutputStream out;
if (enableAsciiArmorOutput) {
if (mEnableAsciiArmorOutput) {
// Ascii Armor (Radix-64)
ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(context));
ArmoredOutputStream armorOut = new ArmoredOutputStream(mOutStream);
armorOut.setHeader("Version", PgpHelper.getFullVersion(mContext));
out = armorOut;
} else {
out = outStream;
out = mOutStream;
}
if (signatureKeyId == 0) {
throw new PgpGeneralException(context.getString(R.string.error_no_signature_key));
if (mSignatureKeyId == 0) {
throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_key));
}
PGPSecretKeyRing signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, signatureKeyId);
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(context, signatureKeyId);
PGPSecretKeyRing signingKeyRing =
ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
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) {
throw new PgpGeneralException(context.getString(R.string.error_no_signature_passphrase));
if (mSignaturePassphrase == null) {
throw new PgpGeneralException(mContext.getString(R.string.error_no_signature_passphrase));
}
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);
if (signaturePrivateKey == null) {
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);
@ -490,12 +470,12 @@ public class PgpSignEncrypt {
// content signer based on signing key algorithm and chosen hash algorithm
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(signingKey
.getPublicKey().getAlgorithm(), signatureHashAlgorithm)
.getPublicKey().getAlgorithm(), mSignatureHashAlgorithm)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator signatureGenerator = null;
PGPV3SignatureGenerator signatureV3Generator = null;
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator = new PGPV3SignatureGenerator(contentSignerBuilder);
signatureV3Generator.init(type, signaturePrivateKey);
} else {
@ -510,7 +490,7 @@ public class PgpSignEncrypt {
updateProgress(R.string.progress_signing, 40, 100);
InputStream inStream = data.getInputStream();
InputStream inStream = mData.getInputStream();
// if (binary) {
// byte[] buffer = new byte[1 << 16];
// int n = 0;
@ -527,7 +507,7 @@ public class PgpSignEncrypt {
String line;
while ((line = reader.readLine()) != null) {
if (signatureForceV3) {
if (mSignatureForceV3) {
processLine(line, null, signatureV3Generator);
signatureV3Generator.update(newline);
} else {
@ -538,13 +518,13 @@ public class PgpSignEncrypt {
// }
BCPGOutputStream bOut = new BCPGOutputStream(out);
if (signatureForceV3) {
if (mSignatureForceV3) {
signatureV3Generator.generate().encode(bOut);
} else {
signatureGenerator.generate().encode(bOut);
}
out.close();
outStream.close();
mOutStream.close();
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;
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.x509.AuthorityKeyIdentifier;
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.asn1.x509.*;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
@ -38,9 +29,23 @@ import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.sufficientlysecure.keychain.Constants;
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 final static 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_O = "OpenPGP to X.509 Bridge";
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
@ -48,20 +53,14 @@ public class PgpToX509 {
* 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.
*
* @param pubKey
* public key
* @param privKey
* private key
* @param subject
* subject (and issuer) DN for this certificate, RFC 2253 format preferred.
* @param startDate
* date from which the certificate will be valid (defaults to current date and time
* @param pubKey public key
* @param privKey private key
* @param subject subject (and issuer) DN for this certificate, RFC 2253 format preferred.
* @param startDate date from which the certificate will be valid (defaults to current date and time
* if null)
* @param endDate
* date until which the certificate will be valid (defaults to current date and time
* @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
* @param subjAltNameURI URI to be placed in subjectAltName
* @return self-signed certificate
* @throws InvalidKeyException
* @throws SignatureException
@ -70,7 +69,6 @@ public class PgpToX509 {
* @throws NoSuchProviderException
* @throws CertificateException
* @throws Exception
*
* @author Bruno Harbulot
*/
public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey,
@ -172,14 +170,11 @@ public class PgpToX509 {
/**
* Creates a self-signed certificate from a PGP Secret Key.
*
* @param pgpSecKey
* PGP Secret Key (from which one can extract the public and private keys and other
* @param pgpSecKey PGP Secret Key (from which one can extract the public and private keys and other
* attributes).
* @param pgpPrivKey
* PGP Private Key corresponding to the Secret Key (password callbacks should be done
* @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks should be done
* before calling this method)
* @param subjAltNameURI
* optional URI to embed in the subject alternative-name
* @param subjAltNameURI optional URI to embed in the subject alternative-name
* @return self-signed certificate
* @throws PGPException
* @throws NoSuchProviderException
@ -187,7 +182,6 @@ public class PgpToX509 {
* @throws NoSuchAlgorithmException
* @throws SignatureException
* @throws CertificateException
*
* @author Bruno Harbulot
*/
public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey,
@ -263,12 +257,11 @@ public class PgpToX509 {
* allow passwords to be stored within your application.
*
* @author Bruno Harbulot.
*
*/
public final static class PredefinedPasswordCallbackHandler implements CallbackHandler {
public static final class PredefinedPasswordCallbackHandler implements CallbackHandler {
private char[] password;
private String prompt;
private char[] mPassword;
private String mPrompt;
public PredefinedPasswordCallbackHandler(String password) {
this(password == null ? null : password.toCharArray(), null);
@ -283,16 +276,16 @@ public class PgpToX509 {
}
public PredefinedPasswordCallbackHandler(char[] password, String prompt) {
this.password = password;
this.prompt = prompt;
this.mPassword = password;
this.mPrompt = prompt;
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof PasswordCallback) {
PasswordCallback pwCallback = (PasswordCallback) callback;
if ((this.prompt == null) || (this.prompt.equals(pwCallback.getPrompt()))) {
pwCallback.setPassword(this.password);
if ((this.mPrompt == null) || (this.mPrompt.equals(pwCallback.getPrompt()))) {
pwCallback.setPassword(this.mPassword);
}
} else {
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;
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;
public class PgpGeneralException extends Exception {

View File

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

View File

@ -17,27 +17,29 @@
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.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
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 {
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 {
String KEY_RINGS = "key_rings";
String KEYS = "keys";
String USER_IDS = "user_ids";
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
@ -62,26 +64,35 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ KeysColumns.KEY_DATA + " BLOB,"
+ KeysColumns.RANK + " INTEGER, "
+ KeysColumns.FINGERPRINT + " BLOB, "
+ KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)";
+ KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, "
+ "FOREIGN KEY(" + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES "
+ Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ UserIdsColumns.USER_ID + " TEXT, "
+ UserIdsColumns.RANK + " INTEGER, "
+ UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)";
+ UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, "
+ "FOREIGN KEY(" + UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES "
+ Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, "
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB, "
+ ApiAppsColumns.KEY_ID + " INT64, "
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB)";
private static final String CREATE_API_APPS_ACCOUNTS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ACCOUNTS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ 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) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -95,6 +106,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
}
@Override
@ -134,6 +146,12 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT
+ " BLOB;");
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:
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>
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -17,21 +17,6 @@
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.ContentValues;
import android.content.UriMatcher;
@ -44,6 +29,22 @@ import android.net.Uri;
import android.provider.BaseColumns;
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 static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME
// + ".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_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_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 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_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;
@ -94,6 +99,15 @@ public class KeychainProvider extends ContentProvider {
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
*
@ -147,6 +161,7 @@ public class KeychainProvider extends ContentProvider {
* <pre>
* key_rings/public/#/user_ids
* key_rings/public/#/user_ids/#
* key_rings/public/master_key_id/#/user_ids
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
@ -155,6 +170,10 @@ public class KeychainProvider extends ContentProvider {
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",
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
@ -220,11 +239,22 @@ public class KeychainProvider extends ContentProvider {
/**
* 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_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", 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
@ -238,7 +268,7 @@ public class KeychainProvider extends ContentProvider {
return matcher;
}
private KeychainDatabase mApgDatabase;
private KeychainDatabase mKeychainDatabase;
/**
* {@inheritDoc}
@ -246,7 +276,7 @@ public class KeychainProvider extends ContentProvider {
@Override
public boolean onCreate() {
mUriMatcher = buildUriMatcher();
mApgDatabase = new KeychainDatabase(getContext());
mKeychainDatabase = new KeychainDatabase(getContext());
return true;
}
@ -282,6 +312,7 @@ public class KeychainProvider extends ContentProvider {
return Keys.CONTENT_ITEM_TYPE;
case PUBLIC_KEY_RING_USER_ID:
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:
case SECRET_KEY_RING_USER_ID:
return UserIds.CONTENT_TYPE;
@ -292,10 +323,15 @@ public class KeychainProvider extends ContentProvider {
case API_APPS:
return ApiApps.CONTENT_TYPE;
case API_APPS_BY_ROW_ID:
case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE;
case API_ACCOUNTS:
return ApiAccounts.CONTENT_TYPE;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
return ApiAccounts.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@ -304,7 +340,7 @@ public class KeychainProvider extends ContentProvider {
/**
* Returns type of the query (secret/public)
*
* @param uri
* @param match
* @return
*/
private int getKeyType(int match) {
@ -319,6 +355,7 @@ public class KeychainProvider extends ContentProvider {
case PUBLIC_KEY_RING_KEY:
case PUBLIC_KEY_RING_KEY_BY_ROW_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:
type = KeyTypes.PUBLIC;
break;
@ -356,15 +393,25 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID);
projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "."
+ 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
//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.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED);
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;
}
@ -395,13 +442,27 @@ public class KeychainProvider extends ContentProvider {
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
*/
private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match) {
if (match != UNIFIED_KEY_RING) {
// public or secret keyring
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
qb.appendWhereEscapeString(Integer.toString(getKeyType(match)));
}
// join keyrings with keys and userIds
// 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) + ")");
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase db = mApgDatabase.getReadableDatabase();
SQLiteDatabase db = mKeychainDatabase.getReadableDatabase();
int match = mUriMatcher.match(uri);
// all query() parameters, for good measure
String groupBy = null, having = null;
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 SECRET_KEY_RING:
qb = buildKeyRingQuery(qb, match);
@ -465,7 +541,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
case PUBLIC_KEY_RING_BY_ROW_ID:
case SECRET_KEY_RING_BY_ROW_ID:
qb = buildKeyRingQuery(qb, match);
@ -478,7 +553,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
qb = buildKeyRingQuery(qb, match);
@ -491,7 +565,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
case SECRET_KEY_RING_BY_KEY_ID:
case PUBLIC_KEY_RING_BY_KEY_ID:
qb = buildKeyRingQueryWithSpecificKey(qb, match);
@ -504,7 +577,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
case SECRET_KEY_RING_BY_EMAILS:
case PUBLIC_KEY_RING_BY_EMAILS:
qb = buildKeyRingQuery(qb, match);
@ -534,7 +606,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
case SECRET_KEY_RING_BY_LIKE_EMAIL:
case PUBLIC_KEY_RING_BY_LIKE_EMAIL:
qb = buildKeyRingQuery(qb, match);
@ -550,7 +621,6 @@ public class KeychainProvider extends ContentProvider {
+ "))");
break;
case PUBLIC_KEY_RING_KEY:
case SECRET_KEY_RING_KEY:
qb.setTables(Tables.KEYS);
@ -563,7 +633,6 @@ public class KeychainProvider extends ContentProvider {
qb.setProjectionMap(getProjectionMapForKeys());
break;
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case SECRET_KEY_RING_KEY_BY_ROW_ID:
qb.setTables(Tables.KEYS);
@ -579,7 +648,16 @@ public class KeychainProvider extends ContentProvider {
qb.setProjectionMap(getProjectionMapForKeys());
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 SECRET_KEY_RING_USER_ID:
qb.setTables(Tables.USER_IDS);
@ -587,7 +665,6 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
break;
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
qb.setTables(Tables.USER_IDS);
@ -598,25 +675,31 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case 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;
case API_APPS_BY_PACKAGE_NAME:
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
qb.appendWhereEscapeString(uri.getLastPathSegment());
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:
throw new IllegalArgumentException("Unknown URI " + uri);
@ -630,7 +713,7 @@ public class KeychainProvider extends ContentProvider {
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
c.setNotificationUri(getContext().getContentResolver(), uri);
@ -653,7 +736,7 @@ public class KeychainProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
Uri rowUri = null;
long rowId = -1;
@ -673,12 +756,14 @@ public class KeychainProvider extends ContentProvider {
values.put(Keys.TYPE, KeyTypes.PUBLIC);
rowId = db.insertOrThrow(Tables.KEYS, null, values);
// TODO: this is wrong:
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
// TODO: this is wrong:
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
@ -695,18 +780,33 @@ public class KeychainProvider extends ContentProvider {
values.put(Keys.TYPE, KeyTypes.SECRET);
rowId = db.insertOrThrow(Tables.KEYS, null, values);
// TODO: this is wrong:
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case SECRET_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
// TODO: this is wrong:
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
break;
case API_APPS:
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;
default:
@ -730,7 +830,7 @@ public class KeychainProvider extends ContentProvider {
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.v(Constants.TAG, "delete(uri=" + uri + ")");
final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
int count;
final int match = mUriMatcher.match(uri);
@ -766,12 +866,12 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
selectionArgs);
break;
case API_APPS_BY_ROW_ID:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection),
case API_APPS_BY_PACKAGE_NAME:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, selection),
selectionArgs);
break;
case API_APPS_BY_PACKAGE_NAME:
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection),
case API_ACCOUNTS_BY_ACCOUNT_NAME:
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, selection),
selectionArgs);
break;
default:
@ -791,7 +891,7 @@ public class KeychainProvider extends ContentProvider {
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = mApgDatabase.getWritableDatabase();
final SQLiteDatabase db = mKeychainDatabase.getWritableDatabase();
String defaultSelection = null;
int count = 0;
@ -836,13 +936,13 @@ public class KeychainProvider extends ContentProvider {
count = db.update(Tables.USER_IDS, values,
buildDefaultUserIdsSelection(uri, selection), selectionArgs);
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:
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;
default:
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
* where clause with rowId
*
* @param uri
* @param defaultSelection
* @param keyType
* @param selection
* @return
*/
@ -940,19 +1041,29 @@ public class KeychainProvider extends ContentProvider {
* @param selection
* @return
*/
private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) {
String lastPathSegment = uri.getLastPathSegment();
private String buildDefaultApiAppsSelection(Uri uri, String selection) {
String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
}
if (packageSelection) {
return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection;
} else {
return BaseColumns._ID + "=" + lastPathSegment + andSelection;
return ApiApps.PACKAGE_NAME + "=" + packageName + 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

View File

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

View File

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

View File

@ -18,11 +18,6 @@
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.ContentUris;
import android.content.ContentValues;
@ -31,7 +26,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
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.FileNotFoundException;
@ -40,7 +38,7 @@ import java.util.List;
import java.util.UUID;
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;
@ -55,7 +53,9 @@ public class KeychainServiceBlobProvider extends ContentProvider {
return true;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public Uri insert(Uri uri, ContentValues ignored) {
// 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);
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException,
FileNotFoundException {
@ -124,26 +126,34 @@ public class KeychainServiceBlobProvider extends ContentProvider {
return null;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public String getType(Uri uri) {
return null;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return null;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;

View File

@ -17,10 +17,11 @@
package org.sufficientlysecure.keychain.provider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import android.content.*;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import org.spongycastle.bcpg.ArmoredOutputStream;
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.UserIds;
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.Log;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
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,
long masterKeyId) {
@ -110,11 +107,8 @@ public class ProviderHelper {
*/
public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long 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) {
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
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()) {
currentRowId = oldQuery.getLong(0);
} else {
@ -184,10 +176,12 @@ public class ProviderHelper {
ContentValues values = new ContentValues();
// 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
if (currentRowId != -1)
if (currentRowId != -1) {
values.put(KeyRings._ID, currentRowId);
}
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
@ -211,8 +205,11 @@ public class ProviderHelper {
++userIdRank;
}
for (PGPSignature certification : 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...
for (PGPSignature certification :
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
// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank));
}
@ -239,7 +236,8 @@ public class ProviderHelper {
// get current _ID of key
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()) {
currentRowId = oldQuery.getLong(0);
} else {
@ -255,10 +253,12 @@ public class ProviderHelper {
ContentValues values = new ContentValues();
// 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
if (currentRowId != -1)
if (currentRowId != -1) {
values.put(KeyRings._ID, currentRowId);
}
values.put(KeyRings.MASTER_KEY_ID, masterKeyId);
values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded());
@ -341,10 +341,10 @@ public class ProviderHelper {
long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
ContentValues values = new ContentValues();
boolean has_private = true;
boolean hasPrivate = true;
if (key.isMasterKey()) {
if (PgpKeyHelper.isSecretKeyPrivateEmpty(key)) {
has_private = false;
if (key.isPrivateKeyEmpty()) {
hasPrivate = false;
}
}
@ -352,9 +352,9 @@ public class ProviderHelper {
values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && has_private));
values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && has_private));
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key) && has_private);
values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && hasPrivate));
values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && hasPrivate));
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key) && hasPrivate);
values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
Date expiryDate = PgpKeyHelper.getExpiryDate(key);
@ -410,6 +410,30 @@ public class ProviderHelper {
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
*/
@ -426,6 +450,22 @@ public class ProviderHelper {
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) {
ContentResolver cr = context.getContentResolver();
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);
}
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
*/
@ -449,13 +498,14 @@ public class ProviderHelper {
*/
public static boolean getSecretMasterKeyCanCertify(Context context, long 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 static boolean getMasterKeyCanCertify(Context context, Uri queryUri, long keyRingRowId) {
public static boolean getMasterKeyCanCertify(Context context, Uri queryUri) {
String[] projection = new String[]{
KeyRings.MASTER_KEY_ID,
"(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
@ -481,6 +531,12 @@ public class ProviderHelper {
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
*/
@ -512,6 +568,26 @@ public class ProviderHelper {
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
*/
@ -733,19 +809,28 @@ public class ProviderHelper {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
values.put(ApiApps.KEY_ID, appSettings.getKeyId());
values.put(ApiApps.COMPRESSION, appSettings.getCompression());
values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
values.put(ApiApps.HASH_ALORITHM, appSettings.getHashAlgorithm());
return values;
}
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;
}
public static void insertApiApp(Context context, AppSettings appSettings) {
context.getContentResolver().insert(ApiApps.CONTENT_URI,
context.getContentResolver().insert(KeychainContract.ApiApps.CONTENT_URI,
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) {
if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null,
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) {
AppSettings settings = null;
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
if (cur != null && cur.moveToFirst()) {
settings = new AppSettings();
settings.setPackageName(cur.getString(cur
.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setPackageSignature(cur.getBlob(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)));
settings.setHashAlgorithm(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.ApiApps.HASH_ALORITHM)));
settings.setEncryptionAlgorithm(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.ApiApps.ENCRYPTION_ALGORITHM)));
settings.setPackageName(cur.getString(
cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setPackageSignature(cur.getBlob(
cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
}
return settings;
}
public static AccountSettings getApiAccountSettings(Context context, Uri uri) {
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;
}
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};

View File

@ -15,80 +15,71 @@
* 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.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Id;
public class AppSettings {
private String packageName;
private byte[] packageSignature;
private long keyId = Id.key.none;
private int encryptionAlgorithm;
private int hashAlgorithm;
private int compression;
public class AccountSettings {
private String mAccountName;
private long mKeyId = Id.key.none;
private int mEncryptionAlgorithm;
private int mHashAlgorithm;
private int mCompression;
public AppSettings() {
public AccountSettings() {
}
public AppSettings(String packageName, byte[] packageSignature) {
public AccountSettings(String accountName) {
super();
this.packageName = packageName;
this.packageSignature = packageSignature;
this.mAccountName = accountName;
// defaults:
this.encryptionAlgorithm = PGPEncryptedData.AES_256;
this.hashAlgorithm = HashAlgorithmTags.SHA512;
this.compression = Id.choice.compression.zlib;
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
this.mCompression = Id.choice.compression.zlib;
}
public String getPackageName() {
return packageName;
public String getAccountName() {
return mAccountName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public byte[] getPackageSignature() {
return packageSignature;
}
public void setPackageSignature(byte[] packageSignature) {
this.packageSignature = packageSignature;
public void setAccountName(String mAccountName) {
this.mAccountName = mAccountName;
}
public long getKeyId() {
return keyId;
return mKeyId;
}
public void setKeyId(long scretKeyId) {
this.keyId = scretKeyId;
this.mKeyId = scretKeyId;
}
public int getEncryptionAlgorithm() {
return encryptionAlgorithm;
return mEncryptionAlgorithm;
}
public void setEncryptionAlgorithm(int encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm;
this.mEncryptionAlgorithm = encryptionAlgorithm;
}
public int getHashAlgorithm() {
return hashAlgorithm;
return mHashAlgorithm;
}
public void setHashAlgorithm(int hashAlgorithm) {
this.hashAlgorithm = hashAlgorithm;
this.mHashAlgorithm = hashAlgorithm;
}
public int getCompression() {
return compression;
return mCompression;
}
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/>.
*/
package org.sufficientlysecure.keychain.service.remote;
package org.sufficientlysecure.keychain.remote;
import android.app.PendingIntent;
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.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@ -47,10 +48,6 @@ import java.util.ArrayList;
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.
*
@ -100,7 +97,9 @@ public class OpenPgpService extends RemoteService {
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
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
Intent result = new Intent();
@ -126,7 +125,9 @@ public class OpenPgpService extends RemoteService {
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
// pass params through to activity that it can be returned again later to repeat pgp operation
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
Intent result = new Intent();
@ -136,7 +137,7 @@ public class OpenPgpService extends RemoteService {
}
private Intent signImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
ParcelFileDescriptor output, AccountSettings accSettings) {
try {
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)) {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId());
}
if (passphrase == null) {
// 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;
}
@ -163,9 +164,9 @@ public class OpenPgpService extends RemoteService {
// sign-only
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor)
.signatureHashAlgorithm(appSettings.getHashAlgorithm())
.signatureHashAlgorithm(accSettings.getHashAlgorithm())
.signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId())
.signatureKeyId(accSettings.getKeyId())
.signaturePassphrase(passphrase);
builder.build().execute();
} finally {
@ -186,7 +187,7 @@ public class OpenPgpService extends RemoteService {
}
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings, boolean sign) {
ParcelFileDescriptor output, AccountSettings accSettings, boolean sign) {
try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@ -208,14 +209,15 @@ public class OpenPgpService extends RemoteService {
} else {
Intent result = new Intent();
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);
return result;
}
// add own key for encryption
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
// Get Input- and OutputStream from ParcelFileDescriptor
@ -227,8 +229,8 @@ public class OpenPgpService extends RemoteService {
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor)
.compressionId(appSettings.getCompression())
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm())
.compressionId(accSettings.getCompression())
.symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
.encryptionKeyIds(keyIds);
if (sign) {
@ -237,18 +239,18 @@ public class OpenPgpService extends RemoteService {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
appSettings.getKeyId());
accSettings.getKeyId());
}
if (passphrase == null) {
// 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;
}
// sign and encrypt
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
builder.signatureHashAlgorithm(accSettings.getHashAlgorithm())
.signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId())
.signatureKeyId(accSettings.getKeyId())
.signaturePassphrase(passphrase);
} else {
// encrypt only
@ -274,7 +276,7 @@ public class OpenPgpService extends RemoteService {
}
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
ParcelFileDescriptor output, AccountSettings accSettings) {
try {
// Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
@ -289,7 +291,8 @@ public class OpenPgpService extends RemoteService {
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
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);
// TODO: currently does not support binary signed-only content
@ -297,7 +300,7 @@ public class OpenPgpService extends RemoteService {
if (decryptVerifyResult.isKeyPassphraseNeeded()) {
// 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;
} else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) {
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_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
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_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
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!
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
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_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
@ -428,17 +434,26 @@ public class OpenPgpService extends RemoteService {
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();
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)) {
return encryptAndSignImpl(data, input, output, appSettings, false);
return encryptAndSignImpl(data, input, output, accSettings, false);
} 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)) {
return decryptAndVerifyImpl(data, input, output, appSettings);
return decryptAndVerifyImpl(data, input, output, accSettings);
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
return getKeyImpl(data);
} 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/>.
*/
package org.sufficientlysecure.keychain.service.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;
package org.sufficientlysecure.keychain.remote;
import android.app.PendingIntent;
import android.app.Service;
@ -39,16 +28,24 @@ import android.content.pm.Signature;
import android.net.Uri;
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.
*/
public abstract class RemoteService extends Service {
Context mContext;
private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
public Context getContext() {
return mContext;
}
@ -56,13 +53,10 @@ public abstract class RemoteService extends Service {
protected Intent isAllowed(Intent data) {
try {
if (isCallerAllowed(false)) {
return null;
} else {
String[] callingPackages = getPackageManager().getPackagesForUid(
Binder.getCallingUid());
// TODO: currently simply uses first entry
String packageName = callingPackages[0];
String packageName = getCurrentCallingPackage();
Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
byte[] packageSignature;
try {
@ -84,7 +78,9 @@ public abstract class RemoteService extends Service {
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
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
Intent result = new Intent();
@ -98,10 +94,13 @@ public abstract class RemoteService extends Service {
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
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);
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
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
*/
protected AppSettings getAppSettings() {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
protected AccountSettings getAccSettings(String accountName) {
String currentPkg = getCurrentCallingPackage();
Log.d(Constants.TAG, "accountName: " + accountName);
// get app settings for this package
for (int i = 0; i < callingPackages.length; i++) {
String currentPkg = callingPackages[i];
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
AccountSettings settings = ProviderHelper.getApiAccountSettings(this, uri);
AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
if (settings != null)
return settings;
return settings; // can be null!
}
return null;
protected Intent getCreateAccountIntent(Intent data, String accountName) {
String packageName = getCurrentCallingPackage();
Log.d(Constants.TAG, "accountName: " + accountName);
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;
}
@ -189,7 +219,7 @@ public abstract class RemoteService extends Service {
* @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);
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;
}

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/>.
*/
package org.sufficientlysecure.keychain.service.remote;
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;
package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent;
import android.net.Uri;
@ -31,17 +25,25 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
public class AppSettingsActivity extends ActionBarActivity {
private Uri mAppUri;
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.remote.AccountSettings;
import org.sufficientlysecure.keychain.util.Log;
private AppSettingsFragment mSettingsFragment;
public class AccountSettingsActivity extends ActionBarActivity {
private Uri mAccountUri;
private AccountSettingsFragment mAccountSettingsFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 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() {
@Override
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(
R.id.api_app_settings_fragment);
mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_account_settings_fragment);
Intent intent = getIntent();
mAppUri = intent.getData();
if (mAppUri == null) {
mAccountUri = intent.getData();
if (mAccountUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
finish();
return;
} else {
Log.d(Constants.TAG, "uri: " + mAppUri);
loadData(mAppUri);
Log.d(Constants.TAG, "uri: " + mAccountUri);
loadData(savedInstanceState, mAccountUri);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.api_app_settings, menu);
getMenuInflater().inflate(R.menu.api_account_settings, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_api_settings_revoke:
revokeAccess();
case R.id.menu_account_settings_delete:
deleteAccount();
return true;
case R.id.menu_api_settings_cancel:
case R.id.menu_account_settings_cancel:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void loadData(Uri appUri) {
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
mSettingsFragment.setAppSettings(settings);
private void loadData(Bundle savedInstanceState, Uri accountUri) {
// TODO: load this also like other fragment with newInstance arguments?
AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri);
mAccountSettingsFragment.setAccSettings(settings);
}
private void revokeAccess() {
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
private void deleteAccount() {
if (getContentResolver().delete(mAccountUri, null, null) <= 0) {
throw new RuntimeException();
}
finish();
}
private void save() {
ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri);
ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri);
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/>.
*/
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.ui.DrawerActivity;
import android.os.Bundle;
public class RegisteredAppsListActivity extends DrawerActivity {
public class AppsListActivity extends DrawerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@ -15,14 +15,12 @@
* 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 org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@ -30,11 +28,22 @@ 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.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> {
// 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() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String selectedPackageName = mAdapter.getItemPackageName(position);
// edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
startActivity(intent);
}
});
@ -71,7 +81,10 @@ public class RegisteredAppsListFragment extends ListFragment implements
}
// 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) {
// 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);
}
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/>.
*/
package org.sufficientlysecure.keychain.service.remote;
package org.sufficientlysecure.keychain.remote.ui;
import android.content.Intent;
import android.os.Bundle;
@ -32,7 +32,10 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
@ -42,6 +45,8 @@ import java.util.ArrayList;
public class RemoteServiceActivity extends ActionBarActivity {
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
+ "API_ACTIVITY_CACHE_PASSPHRASE";
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
@ -58,6 +63,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
// register action
public static final String EXTRA_PACKAGE_NAME = "package_name";
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
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_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";
// register view
private AppSettingsFragment mSettingsFragment;
private AppSettingsFragment mAppSettingsFragment;
// create acc view
private AccountSettingsFragment mAccSettingsFragment;
// select pub keys view
private SelectPublicKeyFragment mSelectFragment;
@ -86,29 +95,26 @@ public class RemoteServiceActivity extends ActionBarActivity {
if (ACTION_REGISTER.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
Log.d(Constants.TAG, "ACTION_REGISTER packageName: "+packageName);
// 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() {
@Override
public void onClick(View v) {
// Allow
// user needs to select a key!
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
mSettingsFragment.setErrorOnSelectKeyFragment(
getString(R.string.api_register_error_select_key));
} else {
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
mSettingsFragment.getAppSettings());
mAppSettingsFragment.getAppSettings());
// give data through for new service call
Intent resultData = extras.getParcelable(EXTRA_DATA);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish();
}
}
}, R.string.api_register_disallow, new View.OnClickListener() {
}, R.string.api_register_disallow, R.drawable.ic_action_cancel,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// 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);
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)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
Intent resultData = extras.getParcelable(EXTRA_DATA);
@ -161,7 +212,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
}
// 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() {
@Override
public void onClick(View v) {
@ -173,7 +225,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
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
public void onClick(View v) {
// 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
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>";
// 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() {
@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
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);

View File

@ -17,47 +17,6 @@
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.content.Context;
import android.content.Intent;
@ -67,12 +26,32 @@ import android.os.Message;
import android.os.Messenger;
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
* data from the activities or other apps, queues these intents, executes them, and stops itself
* 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 */
public static final String EXTRA_MESSENGER = "messenger";
@ -159,6 +138,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// sign key
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_UIDS = "sign_key_uids";
/*
* possible data keys as result send over messenger
@ -324,8 +304,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
builder.enableAsciiArmorOutput(useAsciiArmor)
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
.signatureHashAlgorithm(
Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().generateSignature();
} else if (signOnly) {
@ -333,21 +315,26 @@ public class KeychainIntentService extends IntentService implements ProgressDial
builder.enableAsciiArmorOutput(useAsciiArmor)
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
.signatureHashAlgorithm(
Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().execute();
} else {
Log.d(Constants.TAG, "encrypt...");
builder.enableAsciiArmorOutput(useAsciiArmor)
.compressionId(compressionId)
.symmetricEncryptionAlgorithm(Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
.symmetricEncryptionAlgorithm(
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
.encryptionKeyIds(encryptionKeyIds)
.encryptionPassphrase(encryptionPassphrase)
.signatureKeyId(secretKeyId)
.signatureHashAlgorithm(Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
.signatureHashAlgorithm(
Preferences.getPreferences(this).getDefaultHashAlgorithm())
.signaturePassphrase(
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
builder.build().execute();
}
@ -586,13 +573,24 @@ public class KeychainIntentService extends IntentService implements ProgressDial
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
/* Operation */
int keysTotal = 2;
int keysCreated = 0;
setProgress(
getApplicationContext().getResources().
getQuantityString(R.plurals.progress_generating, keysTotal),
keysCreated,
keysTotal);
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
4096, passphrase, true);
keysCreated++;
setProgress(keysCreated, keysTotal);
PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
4096, passphrase, false);
keysCreated++;
setProgress(keysCreated, keysTotal);
// TODO: default to one master for cert, one sub for encrypt and one sub
// for sign
@ -652,14 +650,11 @@ public class KeychainIntentService extends IntentService implements ProgressDial
if (data.containsKey(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);
// If not exporting all keys get the masterKeyIds of the keys to export from the intent
boolean exportAll = data.getBoolean(EXPORT_ALL);
long keyRingMasterKeyId = -1;
if (!exportAll) {
keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID);
}
/* Operation */
@ -668,27 +663,42 @@ public class KeychainIntentService extends IntentService implements ProgressDial
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
}
// OutputStream
FileOutputStream outStream = new FileOutputStream(outputFile);
ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
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) {
// get all key ring row ids based on export type
if (keyType == Id.type.public_key) {
keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this);
} else {
keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this);
// get all public key ring MasterKey ids
if (keyType == Id.type.public_key || keyType == Id.type.public_secret_key) {
publicMasterKeyIds = allPublicMasterKeyIds;
}
// get all secret key ring MasterKey ids
if (keyType == Id.type.secret_key || keyType == Id.type.public_secret_key) {
secretMasterKeyIds = allSecretMasterKeyIds;
}
} 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);
resultData = pgpImportExport
.exportKeyRings(keyRingMasterKeyIds, keyType, outStream);
if (mIsCanceled) {
boolean isDeleted = new File(outputFile).delete();
}
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
@ -724,48 +734,58 @@ public class KeychainIntentService extends IntentService implements ProgressDial
ArrayList<ImportKeysListEntry> entries = data.getParcelableArrayList(DOWNLOAD_KEY_LIST);
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
HkpKeyServer server = new HkpKeyServer(keyServer);
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();
}
/**
* TODO: copied from ImportKeysListLoader
*
*
* this parses the downloaded key
*/
// need to have access to the bufferedInput, so we can reuse it for the possible
// 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)
while (bufferedInput.available() > 0) {
// create PGPKeyRing object based on downloaded armored key
PGPKeyRing downloadedKey = null;
BufferedInputStream bufferedInput =
new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
if (bufferedInput.available() > 0) {
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
// go through all objects in this block
// get first object in block
Object obj;
while ((obj = objectFactory.nextObject()) != null) {
if ((obj = objectFactory.nextObject()) != null) {
Log.d(Constants.TAG, "Found class: " + obj.getClass());
if (obj instanceof PGPKeyRing) {
PGPKeyRing newKeyring = (PGPKeyRing) obj;
entry.setBytes(newKeyring.getEncoded());
downloadedKey = (PGPKeyRing) obj;
} else {
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
throw new PgpGeneralException("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);
importIntent.setAction(ACTION_IMPORT_KEYRING);
Bundle importData = new Bundle();
@ -786,6 +806,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
/* Input */
long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);
long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID);
ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
/* Operation */
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
@ -793,7 +814,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
signaturePassPhrase);
userIds, signaturePassPhrase);
// store the signed key in our local cache
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
@ -811,10 +832,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
private void sendErrorToHandler(Exception e) {
// Service was canceled. Do not send error to handler.
if (this.mIsCanceled)
if (this.mIsCanceled) {
return;
Log.e(Constants.TAG, "ApgService Exception: ", e);
}
Log.e(Constants.TAG, "KeychainIntentService Exception: ", e);
e.printStackTrace();
Bundle data = new Bundle();
@ -824,9 +845,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) {
// Service was canceled. Do not send message to handler.
if (this.mIsCanceled)
if (this.mIsCanceled) {
return;
}
Message msg = Message.obtain();
msg.arg1 = arg1;
if (arg2 != null) {
@ -877,4 +898,9 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public void setProgress(int progress, int max) {
setProgress(null, progress, max);
}
@Override
public boolean hasServiceStopped() {
return mIsCanceled;
}
}

View File

@ -17,11 +17,7 @@
package org.sufficientlysecure.keychain.service;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.R;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.Bundle;
import android.os.Handler;
@ -29,6 +25,8 @@ import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.widget.Toast;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
public class KeychainIntentServiceHandler extends Handler {
@ -51,25 +49,31 @@ public class KeychainIntentServiceHandler extends Handler {
this.mActivity = activity;
}
public KeychainIntentServiceHandler(Activity activity, ProgressDialogFragment progressDialogFragment) {
public KeychainIntentServiceHandler(Activity activity,
ProgressDialogFragment progressDialogFragment) {
this.mActivity = activity;
this.mProgressDialogFragment = progressDialogFragment;
}
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) {
this(activity, progressDialogMessageId, progressDialogStyle, false, null);
public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
int progressDialogStyle) {
this(activity, progressDialogMessage, progressDialogStyle, false, null);
}
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId,
public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
int progressDialogStyle, boolean cancelable,
OnCancelListener onCancelListener) {
this.mActivity = activity;
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId,
progressDialogStyle, cancelable, onCancelListener);
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
progressDialogMessage,
progressDialogStyle,
cancelable,
onCancelListener);
}
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();
Handler handler = new Handler();
handler.post(new Runnable() {

View File

@ -17,10 +17,16 @@
package org.sufficientlysecure.keychain.service;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import android.app.AlarmManager;
import android.app.PendingIntent;
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.PGPPrivateKey;
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.provider.ProviderHelper;
import android.app.AlarmManager;
import android.app.PendingIntent;
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;
import java.util.Date;
import java.util.Iterator;
/**
* 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
* convenience.
*
*/
public class PassphraseCacheService extends Service {
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
@ -77,7 +68,7 @@ public class PassphraseCacheService extends Service {
private BroadcastReceiver mIntentReceiver;
private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>();
private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
Context mContext;
@ -223,9 +214,9 @@ public class PassphraseCacheService extends Service {
}
}
if (!foundValidKey)
if (!foundValidKey) {
return false;
}
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
"SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
@ -347,7 +338,7 @@ public class PassphraseCacheService extends Service {
Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
// 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!");
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;
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.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
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.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.*;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Spinner;
import android.widget.Toast;
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
*/
public class CertifyKeyActivity extends ActionBarActivity implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
private BootstrapButton mSignButton;
private CheckBox mUploadKeyCheckbox;
private Spinner mSelectKeyserverSpinner;
@ -68,6 +68,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements
private long mPubKeyId = 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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -131,9 +137,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements
finish();
return;
}
Log.e(Constants.TAG, "uri: " + 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) {
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) {
// Message is received after passphrase is cached
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
boolean alreadySigned = false;
/* todo: reconsider this at a later point when certs are in the db
@SuppressWarnings("unchecked")
Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();
while (itr.hasNext()) {
@ -188,6 +276,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
break;
}
}
*/
if (!alreadySigned) {
/*
@ -215,6 +304,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
* kicks off the actual signing process on a background thread
*/
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
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_PUB_KEY_ID, mPubKeyId);
data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds);
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,
R.string.progress_signing, ProgressDialog.STYLE_SPINNER) {
getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -249,7 +348,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
finish();
}
}
};
}
};
// Create a new Messenger for the communication back
@ -281,11 +380,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
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,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -295,7 +394,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
setResult(RESULT_OK);
finish();
}
};
}
};
// Create a new Messenger for the communication back

View File

@ -17,37 +17,6 @@
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.app.ProgressDialog;
import android.content.Intent;
@ -59,16 +28,34 @@ import android.os.Messenger;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationUtils;
import android.widget.CheckBox;
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 android.widget.*;
import com.beardedhen.androidbootstrap.BootstrapButton;
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")
public class DecryptActivity extends DrawerActivity {
@ -383,7 +370,7 @@ public class DecryptActivity extends DrawerActivity {
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
filename = filename.substring(0, filename.length() - 4);
}
mOutputFilename = Constants.path.APP_DIR + "/" + filename;
mOutputFilename = Constants.Path.APP_DIR + "/" + filename;
}
private void updateSource() {
@ -456,8 +443,7 @@ public class DecryptActivity extends DrawerActivity {
getDecryptionKeyFromInputStream();
// if we need a symmetric passphrase or a passphrase to use a secret key ask for it
if (mSecretKeyId == Id.key.symmetric
|| PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
if (mAssumeSymmetricEncryption || PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
showPassphraseDialog();
} else {
if (mDecryptTarget == Id.target.file) {
@ -507,6 +493,7 @@ public class DecryptActivity extends DrawerActivity {
* TODO: Rework function, remove global variables
*/
private void getDecryptionKeyFromInputStream() {
mAssumeSymmetricEncryption = false;
InputStream inStream = null;
if (mContentUri != null) {
try {
@ -546,7 +533,6 @@ public class DecryptActivity extends DrawerActivity {
if (mSecretKeyId == Id.key.none) {
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
}
mAssumeSymmetricEncryption = false;
} catch (NoAsymmetricEncryptionException e) {
if (inStream.markSupported()) {
inStream.reset();
@ -559,6 +545,7 @@ public class DecryptActivity extends DrawerActivity {
mAssumeSymmetricEncryption = true;
}
} 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.STYLE_ALERT).show();
}
@ -644,11 +631,11 @@ public class DecryptActivity extends DrawerActivity {
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,
R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) {
getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -744,8 +731,6 @@ public class DecryptActivity extends DrawerActivity {
}
}
}
;
};
// Create a new Messenger for the communication back

View File

@ -17,30 +17,21 @@
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.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.Menu;
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 android.view.*;
import android.widget.*;
import com.beardedhen.androidbootstrap.FontAwesomeText;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
public class DrawerActivity extends ActionBarActivity {
private DrawerLayout mDrawerLayout;
@ -49,10 +40,8 @@ public class DrawerActivity extends ActionBarActivity {
private CharSequence mDrawerTitle;
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 static final int MENU_ID_PREFERENCE = 222;
@ -62,17 +51,28 @@ public class DrawerActivity extends ActionBarActivity {
mDrawerTitle = getString(R.string.app_name);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
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
// opens
// if the left margin of the loaded layout is close to the
// one used in tablets then set drawer as open and locked
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[]{
new NavItem("fa-user", getString(R.string.nav_contacts)),
new NavItem("fa-lock", getString(R.string.nav_encrypt)),
new NavItem("fa-unlock", getString(R.string.nav_decrypt)),
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))};
mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item,
@ -81,8 +81,11 @@ public class DrawerActivity extends ActionBarActivity {
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
// enable ActionBar app icon to behave as action to toggle nav drawer
// if the drawer is not locked
if ( !mIsDrawerLocked ) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon
@ -94,19 +97,8 @@ public class DrawerActivity extends ActionBarActivity {
) {
public void onDrawerClosed(View view) {
getSupportActionBar().setTitle(mTitle);
// creates call to onPrepareOptionsMenu()
supportInvalidateOptionsMenu();
// call intent activity if selected
if(mSelectedItem != null) {
finish();
overridePendingTransition(0, 0);
Intent intent = new Intent(DrawerActivity.this, mSelectedItem);
startActivity(intent);
// disable animation of activity start
overridePendingTransition(0, 0);
}
callIntentForDrawerItem(mSelectedItem);
}
public void onDrawerOpened(View drawerView) {
@ -116,13 +108,40 @@ public class DrawerActivity extends ActionBarActivity {
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) {
// 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
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences);
@ -193,10 +212,18 @@ public class DrawerActivity extends ActionBarActivity {
private void selectItem(int position) {
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
// setTitle(mDrawerTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
// 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> {
Context context;
int layoutResourceId;
NavItem data[] = null;
Context mContext;
int mLayoutResourceId;
NavItem mData[] = null;
public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
this.mLayoutResourceId = layoutResourceId;
this.mContext = context;
this.mData = data;
}
@Override
@ -246,21 +273,21 @@ public class DrawerActivity extends ActionBarActivity {
NavItemHolder holder = null;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(mLayoutResourceId, parent, false);
holder = new NavItemHolder();
holder.img = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon);
holder.txtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
holder.mImg = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon);
holder.mTxtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
row.setTag(holder);
} else {
holder = (NavItemHolder) row.getTag();
}
NavItem item = data[position];
holder.txtTitle.setText(item.title);
holder.img.setIcon(item.icon);
NavItem item = mData[position];
holder.mTxtTitle.setText(item.title);
holder.mImg.setIcon(item.icon);
return row;
}
@ -268,8 +295,8 @@ public class DrawerActivity extends ActionBarActivity {
}
static class NavItemHolder {
FontAwesomeText img;
TextView txtTitle;
FontAwesomeText mImg;
TextView mTxtTitle;
}
}

View File

@ -23,6 +23,25 @@ import java.util.List;
import java.util.Vector;
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.PGPSecretKeyRing;
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.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;
@ -49,31 +69,12 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
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.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
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 {
@ -113,7 +114,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
Vector<String> mUserIds;
Vector<PGPSecretKey> mKeys;
Vector<Integer> mKeysUsages;
boolean masterCanSign = true;
boolean mMasterCanSign = true;
ExportHelper mExportHelper;
@ -211,9 +212,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
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(
this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER, true,
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_HORIZONTAL, true,
new DialogInterface.OnCancelListener() {
@Override
@ -227,7 +229,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
@Override
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -283,12 +285,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
} else {
Log.d(Constants.TAG, "uri: " + mDataUri);
long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment());
// 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);
}
}
@ -350,11 +350,16 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
if (needsSaving()) {
Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show();
} else {
mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR
+ "/secexport.asc");
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
long[] ids = new long[]{masterKeyId};
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC,
null);
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
Handler returnHandler = new Handler() {
@Override
@ -363,12 +368,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
setResult(RESULT_CANCELED);
finish();
}
}
};
mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler);
}};
mExportHelper.deleteKey(convertUri, returnHandler);
return true;
}
case R.id.menu_key_edit_save:
saveClicked();
return true;
@ -407,8 +410,8 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
}
mCurrentPassphrase = "";
buildLayout(false);
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
if (!mIsPassPhraseSet) {
// check "no passphrase" checkbox and remove button
@ -466,20 +469,23 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// find views
mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
// Build layout based on given userIds and keys
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
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.setType(Id.type.user_id);
mUserIdsView.setCanEdit(masterCanSign);
mUserIdsView.setCanEdit(mMasterCanSign);
mUserIdsView.setUserIds(mUserIds);
mUserIdsView.setEditorListener(this);
container.addView(mUserIdsView);
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
mKeysView.setType(Id.type.key);
mKeysView.setCanEdit(masterCanSign);
mKeysView.setCanEdit(mMasterCanSign);
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
mKeysView.setEditorListener(this);
container.addView(mKeysView);
@ -602,17 +608,16 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// fill values for this action
Bundle data = new Bundle();
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, masterCanSign);
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
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,
R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) {
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -640,6 +645,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// start service with intent
startService(intent);
} 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.LENGTH_SHORT).show();
}
@ -692,11 +698,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
String userId;
try {
userId = editor.getValue();
} catch (UserIdEditor.InvalidEmailException e) {
throw new PgpGeneralException(e.getMessage());
}
if (editor.isMainUserId()) {
userIds.add(0, userId);

View File

@ -17,9 +17,22 @@
package org.sufficientlysecure.keychain.ui;
import java.io.File;
import java.util.Vector;
import android.app.ProgressDialog;
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.PGPPublicKeyRing;
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.Log;
import android.app.ProgressDialog;
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.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;
import java.io.File;
import java.util.Vector;
public class EncryptActivity extends DrawerActivity {
@ -108,6 +102,7 @@ public class EncryptActivity extends DrawerActivity {
private EditText mFilename = null;
private CheckBox mDeleteAfter = null;
private CheckBox mShareAfter = null;
private BootstrapButton mBrowse = null;
private String mInputFilename = null;
@ -602,11 +597,11 @@ public class EncryptActivity extends DrawerActivity {
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,
R.string.progress_encrypting, ProgressDialog.STYLE_HORIZONTAL) {
getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -650,6 +645,15 @@ public class EncryptActivity extends DrawerActivity {
.newInstance(mInputFilename);
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;
default:
@ -659,8 +663,6 @@ public class EncryptActivity extends DrawerActivity {
}
}
}
;
};
// Create a new Messenger for the communication back
@ -785,6 +787,11 @@ public class EncryptActivity extends DrawerActivity {
}
});
mFileCompression = (Spinner) findViewById(R.id.fileCompression);
Choice[] choices = new Choice[]{
new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
@ -809,6 +816,7 @@ public class EncryptActivity extends DrawerActivity {
}
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption);
mShareAfter = (CheckBox) findViewById(R.id.shareAfterEncryption);
mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour);
mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour());
@ -947,8 +955,8 @@ public class EncryptActivity extends DrawerActivity {
case Id.request.secret_keys: {
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
mSecretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
Uri uri_master_key = data.getData();
mSecretKeyId = Long.valueOf(uri_master_key.getLastPathSegment());
} else {
mSecretKeyId = Id.key.none;
}

View File

@ -17,11 +17,6 @@
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.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@ -31,6 +26,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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 {

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
* it under the terms of the GNU General Public License as published by
@ -17,24 +17,16 @@
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.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.v7.app.ActionBar;
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 static final String EXTRA_SELECTED_TAB = "selectedTab";
public static final String EXTRA_SELECTED_TAB = "selected_tab";
ViewPager mViewPager;
TabsAdapter mTabsAdapter;
@ -64,19 +56,24 @@ public class HelpActivity extends ActionBarActivity {
Bundle startBundle = new Bundle();
startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_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();
nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_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();
changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_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)),
HelpAboutFragment.class, null, (selectedTab == 3 ? true : false));
HelpAboutFragment.class, null, (selectedTab == 4));
}
}

View File

@ -17,8 +17,6 @@
package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.htmltextview.HtmlTextView;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -27,11 +25,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import org.sufficientlysecure.htmltextview.HtmlTextView;
public class HelpHtmlFragment extends Fragment {
private Activity mActivity;
private int htmlFile;
private int mHtmlFile;
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) {
mActivity = getActivity();
htmlFile = getArguments().getInt(ARG_HTML_FILE);
mHtmlFile = getArguments().getInt(ARG_HTML_FILE);
ScrollView scroller = new ScrollView(mActivity);
HtmlTextView text = new HtmlTextView(mActivity);
@ -66,7 +65,7 @@ public class HelpHtmlFragment extends Fragment {
scroller.addView(text);
// 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
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.OnClickListener;
import android.widget.ArrayAdapter;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
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)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
if (keyId != 0) {
query = "0x" + PgpKeyHelper.convertKeyToHex(keyId);
query = PgpKeyHelper.convertKeyIdToHex(keyId);
}
} else if (extras.containsKey(EXTRA_FINGERPRINT)) {
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
@ -169,7 +167,8 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
query = "0x" + fingerprint;
}
} 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;
}
@ -360,11 +359,17 @@ 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) {
/**
* Import keys with mImportData
*/
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 ApgHandler first
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -394,17 +399,14 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
.show();
if (bad > 0) {
BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad);
BadImportKeyDialogFragment badImportKeyDialogFragment =
BadImportKeyDialogFragment.newInstance(bad);
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
}
}
}
};
/**
* Import keys with mImportData
*/
public void importKeys() {
if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) {
Log.d(Constants.TAG, "importKeys started");

View File

@ -17,17 +17,15 @@
package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
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 com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
public class ImportKeysClipboardFragment extends Fragment {
@ -60,8 +58,9 @@ public class ImportKeysClipboardFragment extends Fragment {
public void onClick(View v) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
String sendText = "";
if (clipboardText != null)
if (clipboardText != null) {
sendText = clipboardText.toString();
}
mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
}
});

View File

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

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