merge master
5
.gitignore
vendored
@ -31,3 +31,8 @@ pom.xml.*
|
||||
|
||||
#OS Specific
|
||||
[Tt]humbs.db
|
||||
|
||||
|
||||
#Lint output
|
||||
OpenPGP-Keychain/lint-report.html
|
||||
OpenPGP-Keychain/lint-report_files/*
|
@ -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
|
||||
|
16
CHANGELOG
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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 <alice@example.com>"
|
||||
android:id="@+id/crypto_provider_demo_account" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
29
OpenPGP-Keychain-API/libraries/openpgp-api-library/.gitignore
vendored
Normal 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
|
@ -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>
|
202
OpenPGP-Keychain-API/libraries/openpgp-api-library/LICENSE
Normal 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.
|
@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.8.3'
|
||||
classpath 'com.android.tools.build:gradle:0.9.0'
|
||||
}
|
||||
}
|
||||
|
92
OpenPGP-Keychain-API/libraries/openpgp-api-library/build.xml
Normal 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>
|
@ -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 *;
|
||||
#}
|
@ -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
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@ -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)
|
@ -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;
|
@ -1,2 +1,3 @@
|
||||
include ':example-app'
|
||||
include ':libraries:keychain-api-library'
|
||||
include ':libraries:openpgp-api-library'
|
||||
include ':libraries:openkeychain-api-library'
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
@ -149,23 +142,23 @@
|
||||
|
||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-signature" />-->
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-signature" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- DECRYPT with text as extra -->
|
||||
@ -241,7 +234,7 @@
|
||||
<activity
|
||||
android:name=".ui.PreferencesActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_preferences" >
|
||||
android:label="@string/title_preferences">
|
||||
<intent-filter>
|
||||
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@ -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 -->
|
||||
@ -437,4 +435,4 @@
|
||||
<!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> -->
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -37,8 +37,9 @@ 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()
|
||||
: new SDKLevelPriorLevel17Impl());
|
||||
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;
|
||||
|
||||
|
@ -17,28 +17,26 @@
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Set actionbar without home button if called from another app
|
||||
*
|
||||
*
|
||||
* @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());
|
||||
@ -54,30 +52,35 @@ 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);
|
||||
@ -89,22 +92,24 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,12 +26,15 @@ 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 {
|
||||
|
||||
/**
|
||||
* Checks if external storage is mounted if file is located on external storage
|
||||
*
|
||||
*
|
||||
* @param file
|
||||
* @return true if storage is mounted
|
||||
*/
|
||||
@ -52,15 +51,12 @@ public class FileHelper {
|
||||
/**
|
||||
* Opens the preferred installed file manager on Android and shows a toast if no manager is
|
||||
* 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
|
||||
* onActivityResult() in your 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
|
||||
* onActivityResult() in your activity
|
||||
*/
|
||||
public static void openFile(Activity activity, String filename, String mimeType, int requestCode) {
|
||||
Intent intent = buildFileIntent(filename, mimeType);
|
||||
@ -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) {
|
||||
@ -115,21 +110,19 @@ public class FileHelper {
|
||||
+ uri.getPathSegments().toString());
|
||||
|
||||
if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
String[] projection = { "_data" };
|
||||
String[] projection = {"_data"};
|
||||
Cursor cursor = null;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -17,21 +17,22 @@
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Logs bundle content to debug for inspecting the content
|
||||
*
|
||||
*
|
||||
* @param bundle
|
||||
* @param bundleName
|
||||
*/
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,12 +24,17 @@ 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 {
|
||||
|
||||
/**
|
||||
* Convert from byte[] to PGPKeyRing
|
||||
*
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
@ -54,7 +54,7 @@ public class PgpConversionHelper {
|
||||
|
||||
/**
|
||||
* Convert from byte[] to ArrayList<PGPSecretKey>
|
||||
*
|
||||
*
|
||||
* @param keysBytes
|
||||
* @return
|
||||
*/
|
||||
@ -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) {
|
||||
@ -105,13 +105,13 @@ public class PgpConversionHelper {
|
||||
Log.e(Constants.TAG, "Error while converting to PGPSecretKey!", e);
|
||||
}
|
||||
PGPSecretKey secKey = null;
|
||||
if(obj instanceof PGPSecretKey) {
|
||||
if ((secKey = (PGPSecretKey)obj ) == null) {
|
||||
if (obj instanceof PGPSecretKey) {
|
||||
if ((secKey = (PGPSecretKey) obj) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
} else if(obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
|
||||
} else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
|
||||
PGPSecretKeyRing keyRing = null;
|
||||
if ((keyRing = (PGPSecretKeyRing)obj) == null) {
|
||||
if ((keyRing = (PGPSecretKeyRing) obj) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
secKey = keyRing.getSecretKey();
|
||||
@ -122,7 +122,7 @@ public class PgpConversionHelper {
|
||||
|
||||
/**
|
||||
* Convert from ArrayList<PGPSecretKey> to byte[]
|
||||
*
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
@ -141,8 +141,8 @@ public class PgpConversionHelper {
|
||||
|
||||
/**
|
||||
* Convert from PGPSecretKey to byte[]
|
||||
*
|
||||
* @param keysBytes
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
|
||||
@ -157,8 +157,8 @@ public class PgpConversionHelper {
|
||||
|
||||
/**
|
||||
* Convert from PGPSecretKeyRing to byte[]
|
||||
*
|
||||
* @param keysBytes
|
||||
*
|
||||
* @param keyRing
|
||||
* @return
|
||||
*/
|
||||
public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {
|
||||
|
@ -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,34 +664,37 @@ 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,
|
||||
PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
|
||||
private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts,
|
||||
PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
|
||||
boolean validPrimaryKeyBinding = false;
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
@ -140,7 +132,7 @@ public class PgpHelper {
|
||||
|
||||
/**
|
||||
* Generate a random filename
|
||||
*
|
||||
*
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
@ -170,7 +162,7 @@ public class PgpHelper {
|
||||
/**
|
||||
* Go once through stream to get length of stream. The length is later used to display progress
|
||||
* when encrypting/decrypting
|
||||
*
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
* @throws IOException
|
||||
@ -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;
|
||||
|
@ -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));
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
PGPPublicKeyRing publicKeyRing =
|
||||
ProviderHelper.getPGPPublicKeyRingByMasterKeyId(mContext, pubKeyMasterId);
|
||||
|
||||
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);
|
||||
}
|
||||
if (publicKeyRing != null) {
|
||||
publicKeyRing.encode(arOutStream);
|
||||
}
|
||||
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));
|
||||
|
||||
if (publicKeyRing != null) {
|
||||
publicKeyRing.encode(outPub);
|
||||
}
|
||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||
arOutStream.close();
|
||||
return null;
|
||||
}
|
||||
outPub.close();
|
||||
|
||||
arOutStream.close();
|
||||
}
|
||||
|
||||
returnData.putInt(KeychainIntentService.RESULT_EXPORT, keyRingMasterKeyIds.size());
|
||||
// For each secret masterKey id
|
||||
for (long secretKeyMasterId : secretKeyRingMasterIds) {
|
||||
progress++;
|
||||
// Create an output stream
|
||||
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
|
||||
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
||||
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
PGPSecretKeyRing secretKeyRing =
|
||||
ProviderHelper.getPGPSecretKeyRingByMasterKeyId(mContext, secretKeyMasterId);
|
||||
|
||||
if (secretKeyRing != null) {
|
||||
secretKeyRing.encode(arOutStream);
|
||||
}
|
||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||
arOutStream.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
arOutStream.close();
|
||||
}
|
||||
|
||||
returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
@ -466,57 +465,32 @@ public class PgpKeyHelper {
|
||||
String algorithmStr;
|
||||
|
||||
switch (algorithm) {
|
||||
case PGPPublicKey.RSA_ENCRYPT:
|
||||
case PGPPublicKey.RSA_GENERAL:
|
||||
case PGPPublicKey.RSA_SIGN: {
|
||||
algorithmStr = "RSA";
|
||||
break;
|
||||
}
|
||||
case PGPPublicKey.RSA_ENCRYPT:
|
||||
case PGPPublicKey.RSA_GENERAL:
|
||||
case PGPPublicKey.RSA_SIGN: {
|
||||
algorithmStr = "RSA";
|
||||
break;
|
||||
}
|
||||
|
||||
case PGPPublicKey.DSA: {
|
||||
algorithmStr = "DSA";
|
||||
break;
|
||||
}
|
||||
case PGPPublicKey.DSA: {
|
||||
algorithmStr = "DSA";
|
||||
break;
|
||||
}
|
||||
|
||||
case PGPPublicKey.ELGAMAL_ENCRYPT:
|
||||
case PGPPublicKey.ELGAMAL_GENERAL: {
|
||||
algorithmStr = "ElGamal";
|
||||
break;
|
||||
}
|
||||
case PGPPublicKey.ELGAMAL_ENCRYPT:
|
||||
case PGPPublicKey.ELGAMAL_GENERAL: {
|
||||
algorithmStr = "ElGamal";
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
algorithmStr = "Unknown";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
algorithmStr = "Unknown";
|
||||
break;
|
||||
}
|
||||
}
|
||||
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,55 +503,150 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits userId string into naming part, email part, and comment part
|
||||
*
|
||||
*
|
||||
* @param userId
|
||||
* @return array with naming (0), email (1), comment (2)
|
||||
*/
|
||||
public static String[] splitUserId(String userId) {
|
||||
String[] result = new String[] { null, null, null };
|
||||
String[] result = new String[]{null, null, null};
|
||||
|
||||
if (userId == null || userId.equals("")) {
|
||||
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;
|
||||
|
@ -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,20 +54,34 @@ 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;
|
||||
private final ProgressDialogUpdater mProgress;
|
||||
|
||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[] {
|
||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
||||
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
|
||||
SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5,
|
||||
SymmetricKeyAlgorithmTags.TRIPLE_DES };
|
||||
private static final int[] PREFERRED_HASH_ALGORITHMS = new int[] { HashAlgorithmTags.SHA1,
|
||||
HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160 };
|
||||
private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[] {
|
||||
SymmetricKeyAlgorithmTags.TRIPLE_DES};
|
||||
private static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{HashAlgorithmTags.SHA1,
|
||||
HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160};
|
||||
private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
|
||||
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
|
||||
CompressionAlgorithmTags.ZIP };
|
||||
CompressionAlgorithmTags.ZIP};
|
||||
|
||||
public PgpKeyOperation(Context context, ProgressDialogUpdater progress) {
|
||||
super();
|
||||
@ -104,8 +102,9 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
|
||||
boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||
PgpGeneralException, InvalidAlgorithmParameterException {
|
||||
boolean isMasterKey)
|
||||
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||
PgpGeneralException, InvalidAlgorithmParameterException {
|
||||
|
||||
if (keySize < 512) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
|
||||
@ -119,41 +118,41 @@ public class PgpKeyOperation {
|
||||
KeyPairGenerator keyGen;
|
||||
|
||||
switch (algorithmChoice) {
|
||||
case Id.choice.algorithm.dsa: {
|
||||
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
algorithm = PGPPublicKey.DSA;
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.choice.algorithm.elgamal: {
|
||||
if (isMasterKey) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
|
||||
case Id.choice.algorithm.dsa: {
|
||||
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
algorithm = PGPPublicKey.DSA;
|
||||
break;
|
||||
}
|
||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
BigInteger p = Primes.getBestPrime(keySize);
|
||||
BigInteger g = new BigInteger("2");
|
||||
|
||||
ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
|
||||
case Id.choice.algorithm.elgamal: {
|
||||
if (isMasterKey) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
|
||||
}
|
||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
BigInteger p = Primes.getBestPrime(keySize);
|
||||
BigInteger g = new BigInteger("2");
|
||||
|
||||
keyGen.initialize(elParams);
|
||||
algorithm = PGPPublicKey.ELGAMAL_ENCRYPT;
|
||||
break;
|
||||
}
|
||||
ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
|
||||
|
||||
case Id.choice.algorithm.rsa: {
|
||||
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
keyGen.initialize(elParams);
|
||||
algorithm = PGPPublicKey.ELGAMAL_ENCRYPT;
|
||||
break;
|
||||
}
|
||||
|
||||
algorithm = PGPPublicKey.RSA_GENERAL;
|
||||
break;
|
||||
}
|
||||
case Id.choice.algorithm.rsa: {
|
||||
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_unknown_algorithm_choice));
|
||||
}
|
||||
algorithm = PGPPublicKey.RSA_GENERAL;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_unknown_algorithm_choice));
|
||||
}
|
||||
}
|
||||
|
||||
// build new key pair
|
||||
@ -170,11 +169,10 @@ public class PgpKeyOperation {
|
||||
|
||||
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
|
||||
sha1Calc, isMasterKey, keyEncryptor);
|
||||
|
||||
}
|
||||
|
||||
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
|
||||
String newPassPhrase) throws IOException, PGPException {
|
||||
String newPassPhrase) throws IOException, PGPException {
|
||||
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
if (oldPassPhrase == null) {
|
||||
@ -221,15 +219,15 @@ public class PgpKeyOperation {
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
int user_id_index = 0;
|
||||
for (String userId : userIds) {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||
user_id_index++;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
@ -345,7 +343,7 @@ public class PgpKeyOperation {
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
|
||||
public void buildSecretKey(SaveKeyringParcel saveParcel) throws PgpGeneralException,
|
||||
public void buildSecretKey (SaveKeyringParcel saveParcel)throws PgpGeneralException,
|
||||
PGPException, SignatureException, IOException {
|
||||
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
@ -387,7 +385,7 @@ public class PgpKeyOperation {
|
||||
Todo
|
||||
identify more things which need to be preserved - e.g. trust levels?
|
||||
user attributes
|
||||
*/
|
||||
*/
|
||||
|
||||
if (saveParcel.deletedKeys != null) {
|
||||
for (PGPSecretKey dKey : saveParcel.deletedKeys) {
|
||||
@ -546,7 +544,7 @@ public class PgpKeyOperation {
|
||||
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
|
||||
|
||||
for (int i = 1; i < saveParcel.keys.size(); ++i) {
|
||||
updateProgress(40 + 50 * i/ saveParcel.keys.size(), 100);
|
||||
updateProgress(40 + 50 * i / saveParcel.keys.size(), 100);
|
||||
if (saveParcel.moddedKeys[i]) {
|
||||
PGPSecretKey subKey = saveParcel.keys.get(i);
|
||||
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
||||
@ -558,8 +556,8 @@ public class PgpKeyOperation {
|
||||
"".toCharArray());
|
||||
} else {
|
||||
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.oldPassPhrase.toCharArray());
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.oldPassPhrase.toCharArray());
|
||||
}
|
||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
|
||||
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
|
||||
@ -642,50 +640,80 @@ 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 {
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator; {
|
||||
|
||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (certificationKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
PGPPublicKeyRing pubring = ProviderHelper
|
||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
||||
|
||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (certificationKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
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);
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
|
||||
contentSignerBuilder);
|
||||
|
||||
signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
|
||||
PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId),
|
||||
signatureGenerator.generate());
|
||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
||||
|
||||
return pubring;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,30 +29,38 @@ 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
|
||||
* extension is set up with: digital signature, non-repudiation, key-encipherment, key-agreement
|
||||
* 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
|
||||
* if null)
|
||||
* @param endDate
|
||||
* date until which the certificate will be valid (defaults to current date and time
|
||||
* if null) *
|
||||
* @param subjAltNameURI
|
||||
* URI to be placed in subjectAltName
|
||||
*
|
||||
* @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
|
||||
* if null) *
|
||||
* @param subjAltNameURI URI to be placed in subjectAltName
|
||||
* @return self-signed certificate
|
||||
* @throws InvalidKeyException
|
||||
* @throws SignatureException
|
||||
@ -70,11 +69,10 @@ public class PgpToX509 {
|
||||
* @throws NoSuchProviderException
|
||||
* @throws CertificateException
|
||||
* @throws Exception
|
||||
*
|
||||
* @author Bruno Harbulot
|
||||
*/
|
||||
public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey,
|
||||
X509Name subject, Date startDate, Date endDate, String subjAltNameURI)
|
||||
X509Name subject, Date startDate, Date endDate, String subjAltNameURI)
|
||||
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
|
||||
SignatureException, CertificateException, NoSuchProviderException {
|
||||
|
||||
@ -171,15 +169,12 @@ 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
|
||||
* attributes).
|
||||
* @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 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
|
||||
* before calling this method)
|
||||
* @param subjAltNameURI optional URI to embed in the subject alternative-name
|
||||
* @return self-signed certificate
|
||||
* @throws PGPException
|
||||
* @throws NoSuchProviderException
|
||||
@ -187,11 +182,10 @@ public class PgpToX509 {
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws SignatureException
|
||||
* @throws CertificateException
|
||||
*
|
||||
* @author Bruno Harbulot
|
||||
*/
|
||||
public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey,
|
||||
PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException,
|
||||
PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException,
|
||||
NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
|
||||
SignatureException, CertificateException {
|
||||
// get public key from secret key
|
||||
@ -213,7 +207,7 @@ public class PgpToX509 {
|
||||
x509NameValues.add(DN_COMMON_PART_OU);
|
||||
|
||||
for (@SuppressWarnings("unchecked")
|
||||
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext();) {
|
||||
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext(); ) {
|
||||
Object attrib = it.next();
|
||||
x509NameOids.add(X509Name.CN);
|
||||
x509NameValues.add("CryptoCall");
|
||||
@ -225,7 +219,7 @@ public class PgpToX509 {
|
||||
*/
|
||||
Log.d(Constants.TAG, "User attributes: ");
|
||||
for (@SuppressWarnings("unchecked")
|
||||
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext();) {
|
||||
Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext(); ) {
|
||||
Object attrib = it.next();
|
||||
Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass());
|
||||
}
|
||||
@ -261,14 +255,13 @@ public class PgpToX509 {
|
||||
* This is a password callback handler that will fill in a password automatically. Useful to
|
||||
* configure passwords in advance, but should be used with caution depending on how much you
|
||||
* 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.");
|
||||
|
@ -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 {
|
||||
@ -6,4 +23,4 @@ public class NoAsymmetricEncryptionException extends Exception {
|
||||
public NoAsymmetricEncryptionException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -6,4 +23,4 @@ public class PgpGeneralException extends Exception {
|
||||
public PgpGeneralException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
// public or secret keyring
|
||||
qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = ");
|
||||
qb.appendWhereEscapeString(Integer.toString(getKeyType(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
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
@ -91,9 +93,9 @@ public class KeychainServiceBlobProvider extends ContentProvider {
|
||||
|
||||
// get the data
|
||||
SQLiteDatabase db = mBlobDatabase.getReadableDatabase();
|
||||
Cursor result = db.query(KeychainServiceBlobDatabase.TABLE, new String[] { BaseColumns._ID },
|
||||
Cursor result = db.query(KeychainServiceBlobDatabase.TABLE, new String[]{BaseColumns._ID},
|
||||
BaseColumns._ID + " = ? and " + BlobsColumns.KEY + " = ?",
|
||||
new String[] { id, key }, null, null, null);
|
||||
new String[]{id, key}, null, null, null);
|
||||
|
||||
if (result.getCount() == 0) {
|
||||
// either the key is wrong or no id exists
|
||||
@ -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) {
|
||||
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;
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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)) {
|
@ -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);
|
||||
return settings; // can be null!
|
||||
}
|
||||
|
||||
if (settings != null)
|
||||
return settings;
|
||||
}
|
||||
protected Intent getCreateAccountIntent(Intent data, String accountName) {
|
||||
String packageName = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "accountName: " + accountName);
|
||||
|
||||
return null;
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ACC_NAME, accountName);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,7 +207,7 @@ public abstract class RemoteService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "Caller is NOT allowed!");
|
||||
Log.d(Constants.TAG, "Uid is NOT allowed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
return true;
|
||||
case R.id.menu_api_settings_cancel:
|
||||
finish();
|
||||
return true;
|
||||
case R.id.menu_account_settings_delete:
|
||||
deleteAccount();
|
||||
return true;
|
||||
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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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) {
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
||||
mAppSettingsFragment.getAppSettings());
|
||||
|
||||
// give data through for new service call
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
// 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);
|
@ -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 {
|
||||
// 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);
|
||||
|
||||
// read all available blocks... (asc files can contain many blocks with BEGIN END)
|
||||
while (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
// get first object in block
|
||||
Object obj;
|
||||
if ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
PGPKeyRing newKeyring = (PGPKeyRing) obj;
|
||||
|
||||
entry.setBytes(newKeyring.getEncoded());
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
downloadedKey = (PGPKeyRing) obj;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
@ -84,43 +88,43 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
Bundle data = message.getData();
|
||||
|
||||
switch (message.arg1) {
|
||||
case MESSAGE_OKAY:
|
||||
mProgressDialogFragment.dismissAllowingStateLoss();
|
||||
case MESSAGE_OKAY:
|
||||
mProgressDialogFragment.dismissAllowingStateLoss();
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case MESSAGE_EXCEPTION:
|
||||
mProgressDialogFragment.dismissAllowingStateLoss();
|
||||
case MESSAGE_EXCEPTION:
|
||||
mProgressDialogFragment.dismissAllowingStateLoss();
|
||||
|
||||
// show error from service
|
||||
if (data.containsKey(DATA_ERROR)) {
|
||||
Toast.makeText(mActivity,
|
||||
mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MESSAGE_UPDATE_PROGRESS:
|
||||
if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
|
||||
|
||||
// update progress from service
|
||||
if (data.containsKey(DATA_MESSAGE)) {
|
||||
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else if (data.containsKey(DATA_MESSAGE_ID)) {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
|
||||
data.getInt(DATA_PROGRESS_MAX));
|
||||
// show error from service
|
||||
if (data.containsKey(DATA_ERROR)) {
|
||||
Toast.makeText(mActivity,
|
||||
mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
case MESSAGE_UPDATE_PROGRESS:
|
||||
if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) {
|
||||
|
||||
// update progress from service
|
||||
if (data.containsKey(DATA_MESSAGE)) {
|
||||
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else if (data.containsKey(DATA_MESSAGE_ID)) {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
|
||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||
} else {
|
||||
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
|
||||
data.getInt(DATA_PROGRESS_MAX));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -85,7 +76,7 @@ public class PassphraseCacheService extends Service {
|
||||
* This caches a new passphrase in memory by sending a new command to the service. An android
|
||||
* service is only run once. Thus, when the service is already started, new commands just add
|
||||
* new events to the alarm manager for new passphrases to let them timeout in the future.
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @param passphrase
|
||||
@ -105,7 +96,7 @@ public class PassphraseCacheService extends Service {
|
||||
/**
|
||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||
* designed to wait until the service returns the passphrase.
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return passphrase or null (if no passphrase is cached for this keyId)
|
||||
@ -160,7 +151,7 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
/**
|
||||
* Internal implementation to get cached passphrase.
|
||||
*
|
||||
*
|
||||
* @param keyId
|
||||
* @return
|
||||
*/
|
||||
@ -204,7 +195,7 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
/**
|
||||
* Checks if key has a passphrase.
|
||||
*
|
||||
*
|
||||
* @param secretKeyId
|
||||
* @return true if it has a passphrase
|
||||
*/
|
||||
@ -215,17 +206,17 @@ public class PassphraseCacheService extends Service {
|
||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId);
|
||||
PGPSecretKey secretKey = null;
|
||||
boolean foundValidKey = false;
|
||||
for (Iterator keys = secRing.getSecretKeys(); keys.hasNext();) {
|
||||
secretKey = (PGPSecretKey)keys.next();
|
||||
for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) {
|
||||
secretKey = (PGPSecretKey) keys.next();
|
||||
if (!secretKey.isPrivateKeyEmpty()) {
|
||||
foundValidKey = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundValidKey)
|
||||
if (!foundValidKey) {
|
||||
return false;
|
||||
|
||||
}
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
"SC").build("".toCharArray());
|
||||
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
|
||||
@ -268,7 +259,7 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
/**
|
||||
* Build pending intent that is executed by alarm manager to time out a specific passphrase
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
* @return
|
||||
@ -336,7 +327,7 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
/**
|
||||
* Called when one specific passphrase for keyId timed out
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param keyId
|
||||
*/
|
||||
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
@ -87,7 +93,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.getKeyServers());
|
||||
.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mSelectKeyserverSpinner.setAdapter(adapter);
|
||||
|
||||
@ -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
|
||||
|
@ -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 {
|
||||
@ -364,7 +351,7 @@ public class DecryptActivity extends DrawerActivity {
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG,
|
||||
"Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!");
|
||||
"Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!");
|
||||
Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
// end activity
|
||||
@ -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
|
||||
|
@ -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,18 +51,29 @@ 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
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
// 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[] {
|
||||
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)) };
|
||||
new NavItem("fa-android", getString(R.string.nav_apps))};
|
||||
|
||||
mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item,
|
||||
mItemIconTexts));
|
||||
@ -81,32 +81,24 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||
|
||||
// enable ActionBar app icon to behave as action to toggle nav drawer
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
// 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
|
||||
mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
|
||||
mDrawerLayout, /* DrawerLayout object */
|
||||
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
|
||||
R.string.drawer_open, /* "open drawer" description for accessibility */
|
||||
R.string.drawer_close /* "close drawer" description for accessibility */
|
||||
mDrawerLayout, /* DrawerLayout object */
|
||||
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
|
||||
R.string.drawer_open, /* "open drawer" description for accessibility */
|
||||
R.string.drawer_close /* "close drawer" description for accessibility */
|
||||
) {
|
||||
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);
|
||||
@ -150,18 +169,18 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case MENU_ID_PREFERENCE: {
|
||||
Intent intent = new Intent(this, PreferencesActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case MENU_ID_HELP: {
|
||||
Intent intent = new Intent(this, HelpActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
case MENU_ID_PREFERENCE: {
|
||||
Intent intent = new Intent(this, PreferencesActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case MENU_ID_HELP: {
|
||||
Intent intent = new Intent(this, HelpActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// Handle action buttons
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -169,7 +170,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
||||
|
||||
/**
|
||||
* Handle intent action to create new key
|
||||
*
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionCreateKey(Intent intent) {
|
||||
@ -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) {
|
||||
@ -272,7 +274,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
||||
|
||||
/**
|
||||
* Handle intent action to edit existing key
|
||||
*
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionEditKey(Intent intent) {
|
||||
@ -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,8 +645,9 @@ 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();
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -679,7 +685,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
||||
|
||||
/**
|
||||
* Returns user ids from the SectionView
|
||||
*
|
||||
*
|
||||
* @param userIdsView
|
||||
* @return
|
||||
*/
|
||||
@ -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());
|
||||
}
|
||||
userId = editor.getValue();
|
||||
|
||||
if (editor.isMainUserId()) {
|
||||
userIds.add(0, userId);
|
||||
@ -719,7 +721,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
||||
|
||||
/**
|
||||
* Returns keys from the SectionView
|
||||
*
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
@ -742,7 +744,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
|
||||
|
||||
/**
|
||||
* Returns usage selections of keys from the SectionView
|
||||
*
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
|
@ -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) + " ("
|
||||
@ -794,7 +801,7 @@ public class EncryptActivity extends DrawerActivity {
|
||||
new Choice(Id.choice.compression.zlib, "ZLIB ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Id.choice.compression.bzip2, "BZIP2 ("
|
||||
+ getString(R.string.compression_very_slow) + ")"),};
|
||||
+ getString(R.string.compression_very_slow) + ")"), };
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(this,
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -55,7 +54,7 @@ public class HelpAboutFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* Get the current package version.
|
||||
*
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
private String getVersion() {
|
||||
@ -73,4 +72,4 @@ public class HelpAboutFragment extends Fragment {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
@ -52,8 +51,8 @@ public class HelpHtmlFragment extends Fragment {
|
||||
@Override
|
||||
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,11 +65,11 @@ 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));
|
||||
|
||||
return scroller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -338,7 +337,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
// } else {
|
||||
// status.putString(
|
||||
// EXTRA_ERROR,
|
||||
// "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
|
||||
// "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
|
||||
// }
|
||||
// }
|
||||
// } catch (QueryException e) {
|
||||
@ -360,51 +359,54 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
// }
|
||||
|
||||
|
||||
// Message is received after importing is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
|
||||
int updated = returnData
|
||||
.getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
|
||||
int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
|
||||
String toastMessage;
|
||||
if (added > 0 && updated > 0) {
|
||||
String addedStr = getResources().getQuantityString(
|
||||
R.plurals.keys_added_and_updated_1, added, added);
|
||||
String updatedStr = getResources().getQuantityString(
|
||||
R.plurals.keys_added_and_updated_2, updated, updated);
|
||||
toastMessage = addedStr + updatedStr;
|
||||
} else if (added > 0) {
|
||||
toastMessage = getResources().getQuantityString(R.plurals.keys_added,
|
||||
added, added);
|
||||
} else if (updated > 0) {
|
||||
toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
|
||||
updated, updated);
|
||||
} else {
|
||||
toastMessage = getString(R.string.no_keys_added_or_updated);
|
||||
}
|
||||
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
if (bad > 0) {
|
||||
BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad);
|
||||
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Import keys with mImportData
|
||||
*/
|
||||
public void importKeys() {
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
|
||||
int updated = returnData
|
||||
.getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
|
||||
int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
|
||||
String toastMessage;
|
||||
if (added > 0 && updated > 0) {
|
||||
String addedStr = getResources().getQuantityString(
|
||||
R.plurals.keys_added_and_updated_1, added, added);
|
||||
String updatedStr = getResources().getQuantityString(
|
||||
R.plurals.keys_added_and_updated_2, updated, updated);
|
||||
toastMessage = addedStr + updatedStr;
|
||||
} else if (added > 0) {
|
||||
toastMessage = getResources().getQuantityString(R.plurals.keys_added,
|
||||
added, added);
|
||||
} else if (updated > 0) {
|
||||
toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
|
||||
updated, updated);
|
||||
} else {
|
||||
toastMessage = getString(R.string.no_keys_added_or_updated);
|
||||
}
|
||||
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
if (bad > 0) {
|
||||
BadImportKeyDialogFragment badImportKeyDialogFragment =
|
||||
BadImportKeyDialogFragment.newInstance(bad);
|
||||
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) {
|
||||
Log.d(Constants.TAG, "importKeys started");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|