merge master
This commit is contained in:
commit
e1d07214df
353 files changed
+26527
-5755
No files matched your search
@@ -31,3 +31,8 @@ pom.xml.*
|
||||
|
||||
#OS Specific
|
||||
[Tt]humbs.db
|
||||
|
||||
|
||||
#Lint output
|
||||
OpenPGP-Keychain/lint-report.html
|
||||
OpenPGP-Keychain/lint-report_files/*
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+11
-7
@@ -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>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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>
|
||||
@@ -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.
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.8.3'
|
||||
classpath 'com.android.tools.build:gradle:0.9.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
@@ -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)
|
||||
+1
-1
@@ -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;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
-1
@@ -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);
|
||||
|
||||
+3
-2
@@ -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;
|
||||
|
||||
|
||||
+33
-29
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
+58
-48
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+15
-22
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+13
-6
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+27
-28
@@ -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();
|
||||
}
|
||||
}
|
||||
+19
-19
@@ -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) {
|
||||
|
||||
+90
-107
@@ -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);
|
||||
|
||||
+18
-19
@@ -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;
|
||||
|
||||
+74
-66
@@ -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;
|
||||
}
|
||||
|
||||
+160
-92
@@ -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;
|
||||
|
||||
+131
-103
@@ -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;
|
||||
|
||||
+116
-136
@@ -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.");
|
||||
|
||||
+18
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
-17
@@ -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 {
|
||||
|
||||
+38
-20
@@ -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;
|
||||
|
||||
|
||||
+178
-67
@@ -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
|
||||
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
|
||||
-1
@@ -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 {
|
||||
|
||||
+26
-16
@@ -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;
|
||||
|
||||
+170
-56
@@ -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};
|
||||
|
||||
|
||||
+26
-35
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+49
-34
@@ -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)) {
|
||||
+69
-38
@@ -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;
|
||||
}
|
||||
|
||||
+27
@@ -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);
|
||||
}
|
||||
}
|
||||
+34
-31
@@ -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();
|
||||
}
|
||||
+176
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+200
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+138
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+3
-4
@@ -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) {
|
||||
+84
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+77
-24
@@ -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);
|
||||
+130
-104
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
-42
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-33
@@ -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();
|
||||
}
|
||||
|
||||
-243
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-76
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
+131
-32
@@ -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
|
||||
|
||||
+34
-49
@@ -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
|
||||
|
||||
+100
-73
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+57
-55
@@ -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
|
||||
*/
|
||||
|
||||
+40
-32
@@ -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;
|
||||
}
|
||||
|
||||
+6
-7
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+14
-17
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
-46
@@ -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");
|
||||
|
||||
|
||||
+4
-5
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
+5
-7
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
Loaded 100 of 353 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user