Merge branch 'master' into improve-file-more
Conflicts: .gitmodules OpenKeychain/build.gradle OpenKeychain/src/main/AndroidManifest.xml OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java
@@ -1,5 +1,4 @@
|
||||
apply plugin: 'android'
|
||||
apply plugin: 'robolectric'
|
||||
|
||||
dependencies {
|
||||
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
||||
@@ -16,18 +15,11 @@ dependencies {
|
||||
compile project(':extern:spongycastle:pg')
|
||||
compile project(':extern:spongycastle:pkix')
|
||||
compile project(':extern:spongycastle:prov')
|
||||
compile project(':extern:AppMsg:library')
|
||||
compile project(':extern:SuperToasts:supertoasts')
|
||||
compile project(':extern:minidns')
|
||||
compile project(':extern:KeybaseLib:Lib')
|
||||
compile project(':extern:TokenAutoComplete:library')
|
||||
|
||||
// Unit tests are run with Robolectric
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.robolectric:robolectric:2.3'
|
||||
testCompile 'com.squareup:fest-android:1.0.8'
|
||||
testCompile 'com.google.android:android:4.1.1.4'
|
||||
// compile dependencies are automatically also included in testCompile
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -86,7 +78,7 @@ android {
|
||||
|
||||
// NOTE: This disables Lint!
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.equals("lint")) {
|
||||
if (task.name.contains("lint")) {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
For OI Filemanager it makes no difference, gpg files can't be associated
|
||||
-->
|
||||
|
||||
<!-- Specified in buid.gradle -->
|
||||
<!-- Specified in build.gradle -->
|
||||
<!--<uses-sdk-->
|
||||
<!--android:minSdkVersion="9"-->
|
||||
<!--android:targetSdkVersion="19" />-->
|
||||
@@ -49,17 +49,17 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"/>
|
||||
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"/>
|
||||
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
||||
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
||||
|
||||
<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" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||
@@ -84,20 +84,26 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.WizardActivity"
|
||||
android:name=".ui.FirstTimeActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_wizard"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:label="@string/app_name"
|
||||
android:windowSoftInputMode="stateAlwaysHidden" />
|
||||
<activity
|
||||
android:name=".ui.EditKeyActivityOld"
|
||||
android:name=".ui.CreateKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_edit_key"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:label="@string/title_create_key">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ui.KeyListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.EditKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_edit_key"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
android:label="@string/title_edit_key" />
|
||||
<activity
|
||||
android:name=".ui.QrCodeActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/share_qr_code_dialog_title" />
|
||||
<activity
|
||||
android:name=".ui.ViewKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
@@ -221,29 +227,8 @@
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*"/>
|
||||
<!-- Workaround to match files in pathes with dots in them, like /cdcard/my.folder/test.gpg -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/intent_decrypt_file">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
@@ -254,6 +239,101 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Some apps will only respect these file associations
|
||||
if the mimeType is not set, and other apps will only respect them if mimeType is set
|
||||
to */*. Therefore we have two whole copies of the same thing, besides setting the mimeType.
|
||||
-->
|
||||
<intent-filter android:label="@string/intent_decrypt_file">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@@ -323,6 +403,9 @@
|
||||
|
||||
<!-- mime type as defined in http://tools.ietf.org/html/rfc3156 -->
|
||||
<data android:mimeType="application/pgp-keys" />
|
||||
<!-- also link to text/plain, AOSP mail and K-9 mail only give mimeType text/plain
|
||||
when the key file has been manually attached -->
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<!-- NFC: Handle NFC tags detected from outside our application -->
|
||||
<intent-filter>
|
||||
@@ -342,28 +425,8 @@
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
</intent-filter>
|
||||
<!-- VIEW with file endings: *.asc -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="*/*" />
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
@@ -374,7 +437,103 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Some apps will only respect these file associations
|
||||
if the mimeType is not set, and other apps will only respect them if mimeType is set
|
||||
to */*. Therefore we have two whole copies of the same thing, besides setting the mimeType.
|
||||
-->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="*" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<!-- GnuPG ASCII data, mostly keys, but sometimes signatures and encrypted data -->
|
||||
<data android:pathPattern=".*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.asc" />
|
||||
<!-- GnuPG binary encrypted/signed data, binary format -->
|
||||
<data android:pathPattern=".*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.gpg" />
|
||||
<!-- PGP encrypted data, binary format -->
|
||||
<data android:pathPattern=".*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pgp" />
|
||||
<!-- on some mail clients, PGP attachments show up as *.bin -->
|
||||
<data android:pathPattern=".*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- IMPORT_KEY with files TODO: does this work? -->
|
||||
<intent-filter android:label="@string/intent_import_key">
|
||||
@@ -469,31 +628,31 @@
|
||||
|
||||
<service android:name=".service.DummyAccountService">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator"/>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/account_desc"/>
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/account_desc" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.ContactSyncAdapterService">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter"/>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_adapter_desc"/>
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_adapter_desc" />
|
||||
<meta-data
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/custom_pgp_contacts_structure"/>
|
||||
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||
android:resource="@xml/custom_pgp_contacts_structure" />
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".provider.TemporaryStorageProvider"
|
||||
android:authorities="org.sufficientlysecure.keychain.tempstorage"
|
||||
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE"
|
||||
android:exported="true"/>
|
||||
android:exported="true" />
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ public final class Constants {
|
||||
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
|
||||
public static final String KEY_SERVERS = "keyServers";
|
||||
public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion";
|
||||
public static final String CONCEAL_PGP_APPLICATION = "concealPgpApplication";
|
||||
public static final String FIRST_TIME = "firstTime";
|
||||
}
|
||||
|
||||
public static final class Defaults {
|
||||
|
||||
@@ -139,6 +139,16 @@ public class Preferences {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean isFirstTime() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
|
||||
}
|
||||
|
||||
public void setFirstTime(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.Pref.FIRST_TIME, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String[] getKeyServers() {
|
||||
String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,
|
||||
Constants.Defaults.KEY_SERVERS);
|
||||
@@ -187,4 +197,14 @@ public class Preferences {
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConcealPgpApplication(boolean conceal) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.Pref.CONCEAL_PGP_APPLICATION, conceal);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getConcealPgpApplication() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.CONCEAL_PGP_APPLICATION, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.keyimport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* When sending large data (over 1MB) through Androids Binder IPC you get
|
||||
* JavaBinder E !!! FAILED BINDER TRANSACTION !!!
|
||||
* <p/>
|
||||
* To overcome this problem, we cache large Parcelables into a file in our private cache directory
|
||||
* instead of sending them through IPC.
|
||||
*/
|
||||
public class FileImportCache {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private static final String FILENAME = "key_import.pcl";
|
||||
private static final String BUNDLE_DATA = "data";
|
||||
|
||||
public FileImportCache(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void writeCache(ArrayList<ParcelableKeyRing> selectedEntries) throws IOException {
|
||||
Bundle in = new Bundle();
|
||||
in.putParcelableArrayList(BUNDLE_DATA, selectedEntries);
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
File tempFile = new File(mContext.getCacheDir(), FILENAME);
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(tempFile);
|
||||
Parcel p = Parcel.obtain(); // creating empty parcel object
|
||||
in.writeToParcel(p, 0); // saving bundle as parcel
|
||||
fos.write(p.marshall()); // writing parcel to file
|
||||
fos.flush();
|
||||
fos.close();
|
||||
}
|
||||
|
||||
public List<ParcelableKeyRing> readCache() throws IOException {
|
||||
Parcel parcel = Parcel.obtain(); // creating empty parcel object
|
||||
Bundle out;
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
// https://groups.google.com/forum/#!topic/android-developers/-694j87eXVU
|
||||
throw new IOException("cache dir is null!");
|
||||
}
|
||||
|
||||
File tempFile = new File(cacheDir, FILENAME);
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(tempFile);
|
||||
byte[] array = new byte[(int) fis.getChannel().size()];
|
||||
fis.read(array, 0, array.length);
|
||||
fis.close();
|
||||
|
||||
parcel.unmarshall(array, 0, array.length);
|
||||
parcel.setDataPosition(0);
|
||||
out = parcel.readBundle(KeychainApplication.class.getClassLoader());
|
||||
out.putAll(out);
|
||||
|
||||
return out.getParcelableArrayList(BUNDLE_DATA);
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
// delete temp file
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import de.measite.minidns.Client;
|
||||
import de.measite.minidns.Question;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.record.SRV;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.helper.TlsHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@@ -102,8 +103,9 @@ public class HkpKeyserver extends Keyserver {
|
||||
*/
|
||||
public static final Pattern PUB_KEY_LINE = Pattern
|
||||
.compile("pub:([0-9a-fA-F]+):([0-9]+):([0-9]+):([0-9]+):([0-9]*):([rde]*)[ \n\r]*" // pub line
|
||||
+ "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
+ "((uid:([^:]*):([0-9]+):([0-9]*):([rde]*)[ \n\r]*)+)", // one or more uid lines
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
/**
|
||||
* uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags%
|
||||
@@ -215,10 +217,18 @@ public class HkpKeyserver extends Keyserver {
|
||||
throw new HttpError(response, data);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new QueryFailedException("querying server(s) for '" + mHost + "' failed");
|
||||
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Results are sorted by creation date of key!
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
* @throws QueryFailedException
|
||||
* @throws QueryNeedsRepairException
|
||||
*/
|
||||
@Override
|
||||
public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
|
||||
QueryNeedsRepairException {
|
||||
@@ -240,18 +250,26 @@ public class HkpKeyserver extends Keyserver {
|
||||
try {
|
||||
data = query(request);
|
||||
} catch (HttpError e) {
|
||||
if (e.getCode() == 404) {
|
||||
return results;
|
||||
} else {
|
||||
if (e.getData() != null) {
|
||||
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.US));
|
||||
|
||||
if (e.getData().toLowerCase(Locale.US).contains("no keys found")) {
|
||||
// NOTE: This is also a 404 error for some keyservers!
|
||||
return results;
|
||||
} else if (e.getData().toLowerCase(Locale.US).contains("too many")) {
|
||||
throw new TooManyResponsesException();
|
||||
} else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) {
|
||||
throw new QueryTooShortException();
|
||||
} else if (e.getCode() == 404) {
|
||||
// NOTE: handle this 404 at last, maybe it was a "no keys found" error
|
||||
throw new QueryFailedException("Keyserver '" + mHost + "' not found. Error 404");
|
||||
} else {
|
||||
// NOTE: some keyserver do not provide a more detailed error response
|
||||
throw new QueryTooShortOrTooManyResponsesException();
|
||||
}
|
||||
}
|
||||
throw new QueryFailedException("querying server(s) for '" + mHost + "' failed");
|
||||
|
||||
throw new QueryFailedException("Querying server(s) for '" + mHost + "' failed.");
|
||||
}
|
||||
|
||||
final Matcher matcher = PUB_KEY_LINE.matcher(data);
|
||||
@@ -291,7 +309,7 @@ public class HkpKeyserver extends Keyserver {
|
||||
while (uidMatcher.find()) {
|
||||
String tmp = uidMatcher.group(1).trim();
|
||||
if (tmp.contains("%")) {
|
||||
if(tmp.contains("%%")) {
|
||||
if (tmp.contains("%%")) {
|
||||
// This is a fix for issue #683
|
||||
// The server encodes a percent sign as %%, so it is swapped out with its
|
||||
// urlencoded counterpart to prevent errors
|
||||
|
||||
@@ -250,7 +250,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
|
||||
mHashCode = key.hashCode();
|
||||
|
||||
mPrimaryUserId = key.getPrimaryUserId();
|
||||
mPrimaryUserId = key.getPrimaryUserIdWithFallback();
|
||||
mUserIds = key.getUnorderedUserIds();
|
||||
|
||||
// if there was no user id flagged as primary, use the first one
|
||||
|
||||
@@ -44,6 +44,13 @@ public abstract class Keyserver {
|
||||
private static final long serialVersionUID = 2703768928624654514L;
|
||||
}
|
||||
|
||||
/**
|
||||
* query too short _or_ too many responses
|
||||
*/
|
||||
public static class QueryTooShortOrTooManyResponsesException extends QueryNeedsRepairException {
|
||||
private static final long serialVersionUID = 2703768928624654514L;
|
||||
}
|
||||
|
||||
public static class AddKeyException extends Exception {
|
||||
private static final long serialVersionUID = -507574859137295530L;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,11 @@ import java.io.OutputStream;
|
||||
* getter method.
|
||||
*
|
||||
*/
|
||||
public abstract class WrappedKeyRing extends KeyRing {
|
||||
public abstract class CanonicalizedKeyRing extends KeyRing {
|
||||
|
||||
private final boolean mHasAnySecret;
|
||||
private final int mVerified;
|
||||
|
||||
WrappedKeyRing(boolean hasAnySecret, int verified) {
|
||||
mHasAnySecret = hasAnySecret;
|
||||
CanonicalizedKeyRing(int verified) {
|
||||
mVerified = verified;
|
||||
}
|
||||
|
||||
@@ -30,17 +28,17 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
return getRing().getPublicKey().getKeyID();
|
||||
}
|
||||
|
||||
public boolean hasAnySecret() {
|
||||
return mHasAnySecret;
|
||||
}
|
||||
|
||||
public int getVerified() {
|
||||
return mVerified;
|
||||
}
|
||||
|
||||
public String getPrimaryUserId() throws PgpGeneralException {
|
||||
return (String) getRing().getPublicKey().getUserIDs().next();
|
||||
};
|
||||
return getPublicKey().getPrimaryUserId();
|
||||
}
|
||||
|
||||
public String getPrimaryUserIdWithFallback() throws PgpGeneralException {
|
||||
return getPublicKey().getPrimaryUserIdWithFallback();
|
||||
}
|
||||
|
||||
public boolean isRevoked() throws PgpGeneralException {
|
||||
// Is the master key revoked?
|
||||
@@ -52,7 +50,7 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
}
|
||||
|
||||
public long getEncryptId() throws PgpGeneralException {
|
||||
for(WrappedPublicKey key : publicKeyIterator()) {
|
||||
for(CanonicalizedPublicKey key : publicKeyIterator()) {
|
||||
if(key.canEncrypt()) {
|
||||
return key.getKeyId();
|
||||
}
|
||||
@@ -70,7 +68,7 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
}
|
||||
|
||||
public long getSignId() throws PgpGeneralException {
|
||||
for(WrappedPublicKey key : publicKeyIterator()) {
|
||||
for(CanonicalizedPublicKey key : publicKeyIterator()) {
|
||||
if(key.canSign()) {
|
||||
return key.getKeyId();
|
||||
}
|
||||
@@ -99,10 +97,18 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
|
||||
abstract PGPKeyRing getRing();
|
||||
|
||||
abstract public IterableIterator<WrappedPublicKey> publicKeyIterator();
|
||||
abstract public IterableIterator<CanonicalizedPublicKey> publicKeyIterator();
|
||||
|
||||
public UncachedKeyRing getUncached() {
|
||||
return new UncachedKeyRing(getRing());
|
||||
public CanonicalizedPublicKey getPublicKey() {
|
||||
return new CanonicalizedPublicKey(this, getRing().getPublicKey());
|
||||
}
|
||||
|
||||
public CanonicalizedPublicKey getPublicKey(long id) {
|
||||
return new CanonicalizedPublicKey(this, getRing().getPublicKey(id));
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return getRing().getEncoded();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,12 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
* stored in the database.
|
||||
*
|
||||
*/
|
||||
public class WrappedPublicKey extends UncachedPublicKey {
|
||||
public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
// this is the parent key ring
|
||||
final KeyRing mRing;
|
||||
|
||||
WrappedPublicKey(KeyRing ring, PGPPublicKey key) {
|
||||
CanonicalizedPublicKey(KeyRing ring, PGPPublicKey key) {
|
||||
super(key);
|
||||
mRing = ring;
|
||||
}
|
||||
@@ -1,42 +1,45 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
private PGPPublicKeyRing mRing;
|
||||
private final byte[] mPubKey;
|
||||
|
||||
public WrappedPublicKeyRing(byte[] blob, boolean hasAnySecret, int verified) {
|
||||
super(hasAnySecret, verified);
|
||||
mPubKey = blob;
|
||||
CanonicalizedPublicKeyRing(PGPPublicKeyRing ring, int verified) {
|
||||
super(verified);
|
||||
mRing = ring;
|
||||
}
|
||||
|
||||
public CanonicalizedPublicKeyRing(byte[] blob, int verified) {
|
||||
super(verified);
|
||||
if(mRing == null) {
|
||||
// get first object in block
|
||||
PGPObjectFactory factory = new PGPObjectFactory(blob);
|
||||
try {
|
||||
Object obj = factory.nextObject();
|
||||
if (! (obj instanceof PGPPublicKeyRing)) {
|
||||
throw new RuntimeException("Error constructing CanonicalizedPublicKeyRing, should never happen!");
|
||||
}
|
||||
mRing = (PGPPublicKeyRing) obj;
|
||||
if (factory.nextObject() != null) {
|
||||
throw new RuntimeException("Encountered trailing data after keyring, should never happen!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO Error constructing CanonicalizedPublicKeyRing, should never happen!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PGPPublicKeyRing getRing() {
|
||||
if(mRing == null) {
|
||||
PGPObjectFactory factory = new PGPObjectFactory(mPubKey);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
if ((keyRing = (PGPKeyRing) factory.nextObject()) == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Error while converting to PGPKeyRing!", e);
|
||||
}
|
||||
|
||||
mRing = (PGPPublicKeyRing) keyRing;
|
||||
}
|
||||
return mRing;
|
||||
}
|
||||
|
||||
@@ -44,19 +47,11 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
getRing().encode(stream);
|
||||
}
|
||||
|
||||
public WrappedPublicKey getSubkey() {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey());
|
||||
}
|
||||
|
||||
public WrappedPublicKey getSubkey(long id) {
|
||||
return new WrappedPublicKey(this, getRing().getPublicKey(id));
|
||||
}
|
||||
|
||||
/** Getter that returns the subkey that should be used for signing. */
|
||||
WrappedPublicKey getEncryptionSubKey() throws PgpGeneralException {
|
||||
CanonicalizedPublicKey getEncryptionSubKey() throws PgpGeneralException {
|
||||
PGPPublicKey key = getRing().getPublicKey(getEncryptId());
|
||||
if(key != null) {
|
||||
WrappedPublicKey cKey = new WrappedPublicKey(this, key);
|
||||
CanonicalizedPublicKey cKey = new CanonicalizedPublicKey(this, key);
|
||||
if(!cKey.canEncrypt()) {
|
||||
throw new PgpGeneralException("key error");
|
||||
}
|
||||
@@ -65,18 +60,18 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {
|
||||
throw new PgpGeneralException("no encryption key available");
|
||||
}
|
||||
|
||||
public IterableIterator<WrappedPublicKey> publicKeyIterator() {
|
||||
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
|
||||
return new IterableIterator<WrappedPublicKey>(new Iterator<WrappedPublicKey>() {
|
||||
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKey next() {
|
||||
return new WrappedPublicKey(WrappedPublicKeyRing.this, it.next());
|
||||
public CanonicalizedPublicKey next() {
|
||||
return new CanonicalizedPublicKey(CanonicalizedPublicKeyRing.this, it.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,26 +37,18 @@ import java.util.List;
|
||||
* properly imported secret keys only.
|
||||
*
|
||||
*/
|
||||
public class WrappedSecretKey extends WrappedPublicKey {
|
||||
public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
private final PGPSecretKey mSecretKey;
|
||||
private PGPPrivateKey mPrivateKey = null;
|
||||
|
||||
WrappedSecretKey(WrappedSecretKeyRing ring, PGPSecretKey key) {
|
||||
CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) {
|
||||
super(ring, key.getPublicKey());
|
||||
mSecretKey = key;
|
||||
}
|
||||
|
||||
public WrappedSecretKeyRing getRing() {
|
||||
return (WrappedSecretKeyRing) mRing;
|
||||
}
|
||||
|
||||
/** Returns the wrapped PGPSecretKeyRing.
|
||||
* This function is for compatibility only, should not be used anymore and will be removed
|
||||
*/
|
||||
@Deprecated
|
||||
public PGPSecretKey getKeyExternal() {
|
||||
return mSecretKey;
|
||||
public CanonicalizedSecretKeyRing getRing() {
|
||||
return (CanonicalizedSecretKeyRing) mRing;
|
||||
}
|
||||
|
||||
public boolean unlock(String passphrase) throws PgpGeneralException {
|
||||
@@ -97,7 +89,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
signatureGenerator.init(signatureType, mPrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
spGen.setSignerUserID(false, mRing.getPrimaryUserId());
|
||||
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
return signatureGenerator;
|
||||
} catch(PGPException e) {
|
||||
@@ -148,7 +140,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
* @param userIds User IDs to certify, must not be null or empty
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public UncachedKeyRing certifyUserIds(WrappedPublicKeyRing publicKeyRing, List<String> userIds)
|
||||
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds)
|
||||
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
PGPException, SignatureException {
|
||||
|
||||
@@ -175,7 +167,7 @@ public class WrappedSecretKey extends WrappedPublicKey {
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicKeyRing.getSubkey().getPublicKey();
|
||||
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (String userId : new IterableIterator<String>(userIds.iterator())) {
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
@@ -15,15 +17,21 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
private PGPSecretKeyRing mRing;
|
||||
|
||||
public WrappedSecretKeyRing(byte[] blob, boolean isRevoked, int verified)
|
||||
CanonicalizedSecretKeyRing(PGPSecretKeyRing ring, int verified) {
|
||||
super(verified);
|
||||
mRing = ring;
|
||||
}
|
||||
|
||||
public CanonicalizedSecretKeyRing(byte[] blob, boolean isRevoked, int verified)
|
||||
{
|
||||
super(isRevoked, verified);
|
||||
super(verified);
|
||||
PGPObjectFactory factory = new PGPObjectFactory(blob);
|
||||
PGPKeyRing keyRing = null;
|
||||
try {
|
||||
@@ -41,19 +49,32 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public WrappedSecretKey getSubKey() {
|
||||
return new WrappedSecretKey(this, mRing.getSecretKey());
|
||||
public CanonicalizedSecretKey getSecretKey() {
|
||||
return new CanonicalizedSecretKey(this, mRing.getSecretKey());
|
||||
}
|
||||
|
||||
public WrappedSecretKey getSubKey(long id) {
|
||||
return new WrappedSecretKey(this, mRing.getSecretKey(id));
|
||||
public CanonicalizedSecretKey getSecretKey(long id) {
|
||||
return new CanonicalizedSecretKey(this, mRing.getSecretKey(id));
|
||||
}
|
||||
|
||||
public HashSet<Long> getAvailableSubkeys() {
|
||||
HashSet<Long> result = new HashSet<Long>();
|
||||
// then, mark exactly the keys we have available
|
||||
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(getRing().getSecretKeys())) {
|
||||
S2K s2k = sub.getS2K();
|
||||
// Set to 1, except if the encryption type is GNU_DUMMY_S2K
|
||||
if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) {
|
||||
result.add(sub.getKeyID());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Getter that returns the subkey that should be used for signing. */
|
||||
WrappedSecretKey getSigningSubKey() throws PgpGeneralException {
|
||||
CanonicalizedSecretKey getSigningSubKey() throws PgpGeneralException {
|
||||
PGPSecretKey key = mRing.getSecretKey(getSignId());
|
||||
if(key != null) {
|
||||
WrappedSecretKey cKey = new WrappedSecretKey(this, key);
|
||||
CanonicalizedSecretKey cKey = new CanonicalizedSecretKey(this, key);
|
||||
if(!cKey.canSign()) {
|
||||
throw new PgpGeneralException("key error");
|
||||
}
|
||||
@@ -88,17 +109,17 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
public IterableIterator<WrappedSecretKey> secretKeyIterator() {
|
||||
public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() {
|
||||
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
|
||||
return new IterableIterator<WrappedSecretKey>(new Iterator<WrappedSecretKey>() {
|
||||
return new IterableIterator<CanonicalizedSecretKey>(new Iterator<CanonicalizedSecretKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedSecretKey next() {
|
||||
return new WrappedSecretKey(WrappedSecretKeyRing.this, it.next());
|
||||
public CanonicalizedSecretKey next() {
|
||||
return new CanonicalizedSecretKey(CanonicalizedSecretKeyRing.this, it.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,17 +129,17 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
|
||||
});
|
||||
}
|
||||
|
||||
public IterableIterator<WrappedPublicKey> publicKeyIterator() {
|
||||
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
|
||||
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
|
||||
return new IterableIterator<WrappedPublicKey>(new Iterator<WrappedPublicKey>() {
|
||||
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKey next() {
|
||||
return new WrappedPublicKey(WrappedSecretKeyRing.this, it.next());
|
||||
public CanonicalizedPublicKey next() {
|
||||
return new CanonicalizedPublicKey(CanonicalizedSecretKeyRing.this, it.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -12,7 +12,7 @@ import java.util.regex.Pattern;
|
||||
* keyring should in all cases agree on the output of all methods described
|
||||
* here.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.WrappedKeyRing
|
||||
* @see CanonicalizedKeyRing
|
||||
* @see org.sufficientlysecure.keychain.provider.CachedPublicKeyRing
|
||||
*
|
||||
*/
|
||||
@@ -22,8 +22,10 @@ public abstract class KeyRing {
|
||||
|
||||
abstract public String getPrimaryUserId() throws PgpGeneralException;
|
||||
|
||||
public String[] getSplitPrimaryUserId() throws PgpGeneralException {
|
||||
return splitUserId(getPrimaryUserId());
|
||||
abstract public String getPrimaryUserIdWithFallback() throws PgpGeneralException;
|
||||
|
||||
public String[] getSplitPrimaryUserIdWithFallback() throws PgpGeneralException {
|
||||
return splitUserId(getPrimaryUserIdWithFallback());
|
||||
}
|
||||
|
||||
abstract public boolean isRevoked() throws PgpGeneralException;
|
||||
|
||||
@@ -231,7 +231,7 @@ public class PgpDecryptVerify {
|
||||
|
||||
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||
WrappedSecretKey secretEncryptionKey = null;
|
||||
CanonicalizedSecretKey secretEncryptionKey = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
boolean asymmetricPacketFound = false;
|
||||
boolean symmetricPacketFound = false;
|
||||
@@ -243,10 +243,10 @@ public class PgpDecryptVerify {
|
||||
|
||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||
|
||||
WrappedSecretKeyRing secretKeyRing;
|
||||
CanonicalizedSecretKeyRing secretKeyRing;
|
||||
try {
|
||||
// get actual keyring object based on master key id
|
||||
secretKeyRing = mProviderHelper.getWrappedSecretKeyRing(
|
||||
secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(encData.getKeyID())
|
||||
);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
@@ -258,7 +258,7 @@ public class PgpDecryptVerify {
|
||||
continue;
|
||||
}
|
||||
// get subkey which has been used for this encryption packet
|
||||
secretEncryptionKey = secretKeyRing.getSubKey(encData.getKeyID());
|
||||
secretEncryptionKey = secretKeyRing.getSecretKey(encData.getKeyID());
|
||||
if (secretEncryptionKey == null) {
|
||||
// continue with the next packet in the while loop
|
||||
continue;
|
||||
@@ -365,8 +365,8 @@ public class PgpDecryptVerify {
|
||||
Object dataChunk = plainFact.nextObject();
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
int signatureIndex = -1;
|
||||
WrappedPublicKeyRing signingRing = null;
|
||||
WrappedPublicKey signingKey = null;
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
|
||||
if (dataChunk instanceof PGPCompressedData) {
|
||||
updateProgress(R.string.progress_decompressing_data, currentProgress, 100);
|
||||
@@ -390,10 +390,10 @@ public class PgpDecryptVerify {
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getSubkey(sigKeyId);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found!");
|
||||
@@ -409,7 +409,7 @@ public class PgpDecryptVerify {
|
||||
signatureResultBuilder.knownKey(true);
|
||||
signatureResultBuilder.keyId(signingRing.getMasterKeyId());
|
||||
try {
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserId());
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback());
|
||||
} catch(PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId());
|
||||
}
|
||||
@@ -566,8 +566,8 @@ public class PgpDecryptVerify {
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
WrappedPublicKeyRing signingRing = null;
|
||||
WrappedPublicKey signingKey = null;
|
||||
CanonicalizedPublicKeyRing signingRing = null;
|
||||
CanonicalizedPublicKey signingKey = null;
|
||||
int signatureIndex = -1;
|
||||
|
||||
// go through all signatures
|
||||
@@ -575,10 +575,10 @@ public class PgpDecryptVerify {
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
try {
|
||||
long sigKeyId = sigList.get(i).getKeyID();
|
||||
signingRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
|
||||
);
|
||||
signingKey = signingRing.getSubkey(sigKeyId);
|
||||
signingKey = signingRing.getPublicKey(sigKeyId);
|
||||
signatureIndex = i;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.d(Constants.TAG, "key not found!");
|
||||
@@ -596,7 +596,7 @@ public class PgpDecryptVerify {
|
||||
signatureResultBuilder.knownKey(true);
|
||||
signatureResultBuilder.keyId(signingRing.getMasterKeyId());
|
||||
try {
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserId());
|
||||
signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback());
|
||||
} catch(PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId());
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
@@ -60,7 +61,11 @@ public class PgpHelper {
|
||||
}
|
||||
|
||||
public static String getFullVersion(Context context) {
|
||||
return "OpenKeychain v" + getVersion(context);
|
||||
if(Preferences.getPreferences(context).getConcealPgpApplication()){
|
||||
return "";
|
||||
} else {
|
||||
return "OpenKeychain v" + getVersion(context);
|
||||
}
|
||||
}
|
||||
|
||||
// public static final class content {
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@@ -93,7 +93,7 @@ public class PgpImportExport {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean uploadKeyRingToServer(HkpKeyserver server, WrappedPublicKeyRing keyring) {
|
||||
public boolean uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream aos = null;
|
||||
try {
|
||||
@@ -123,14 +123,14 @@ public class PgpImportExport {
|
||||
}
|
||||
|
||||
/** Imports keys from given data. If keyIds is given only those are imported */
|
||||
public ImportResult importKeyRings(List<ParcelableKeyRing> entries) {
|
||||
public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries) {
|
||||
|
||||
updateProgress(R.string.progress_importing, 0, 100);
|
||||
|
||||
// If there aren't even any keys, do nothing here.
|
||||
if (entries == null || entries.size() == 0) {
|
||||
return new ImportResult(
|
||||
ImportResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0);
|
||||
return new ImportKeyResult(
|
||||
ImportKeyResult.RESULT_FAIL_NOTHING, mProviderHelper.getLog(), 0, 0, 0);
|
||||
}
|
||||
|
||||
int newKeys = 0, oldKeys = 0, badKeys = 0;
|
||||
@@ -185,26 +185,26 @@ public class PgpImportExport {
|
||||
int resultType = 0;
|
||||
// special return case: no new keys at all
|
||||
if (badKeys == 0 && newKeys == 0 && oldKeys == 0) {
|
||||
resultType = ImportResult.RESULT_FAIL_NOTHING;
|
||||
resultType = ImportKeyResult.RESULT_FAIL_NOTHING;
|
||||
} else {
|
||||
if (newKeys > 0) {
|
||||
resultType |= ImportResult.RESULT_OK_NEWKEYS;
|
||||
resultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
|
||||
}
|
||||
if (oldKeys > 0) {
|
||||
resultType |= ImportResult.RESULT_OK_UPDATED;
|
||||
resultType |= ImportKeyResult.RESULT_OK_UPDATED;
|
||||
}
|
||||
if (badKeys > 0) {
|
||||
resultType |= ImportResult.RESULT_WITH_ERRORS;
|
||||
resultType |= ImportKeyResult.RESULT_WITH_ERRORS;
|
||||
if (newKeys == 0 && oldKeys == 0) {
|
||||
resultType |= ImportResult.RESULT_ERROR;
|
||||
resultType |= ImportKeyResult.RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
if (log.containsWarnings()) {
|
||||
resultType |= ImportResult.RESULT_WITH_WARNINGS;
|
||||
resultType |= ImportKeyResult.RESULT_WITH_WARNINGS;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportResult(resultType, log, newKeys, oldKeys, badKeys);
|
||||
return new ImportKeyResult(resultType, log, newKeys, oldKeys, badKeys);
|
||||
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class PgpImportExport {
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
|
||||
try {
|
||||
WrappedPublicKeyRing ring = mProviderHelper.getWrappedPublicKeyRing(
|
||||
CanonicalizedPublicKeyRing ring = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingUri(pubKeyMasterId)
|
||||
);
|
||||
|
||||
@@ -263,8 +263,8 @@ public class PgpImportExport {
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
|
||||
try {
|
||||
WrappedSecretKeyRing secretKeyRing =
|
||||
mProviderHelper.getWrappedSecretKeyRing(secretKeyMasterId);
|
||||
CanonicalizedSecretKeyRing secretKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(secretKeyMasterId);
|
||||
secretKeyRing.encode(arOutStream);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
|
||||
@@ -46,15 +46,17 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Primes;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
@@ -65,10 +67,9 @@ import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||
@@ -80,7 +81,7 @@ import java.util.TimeZone;
|
||||
* This indicator may be null.
|
||||
*/
|
||||
public class PgpKeyOperation {
|
||||
private Progressable mProgress;
|
||||
private Stack<Progressable> mProgress;
|
||||
|
||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
||||
SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192,
|
||||
@@ -94,21 +95,43 @@ public class PgpKeyOperation {
|
||||
|
||||
public PgpKeyOperation(Progressable progress) {
|
||||
super();
|
||||
this.mProgress = progress;
|
||||
}
|
||||
|
||||
void updateProgress(int message, int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(message, current, total);
|
||||
if (progress != null) {
|
||||
mProgress = new Stack<Progressable>();
|
||||
mProgress.push(progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void subProgressPush(int from, int to) {
|
||||
if (mProgress == null) {
|
||||
return;
|
||||
}
|
||||
mProgress.push(new ProgressScaler(mProgress.peek(), from, to, 100));
|
||||
}
|
||||
private void subProgressPop() {
|
||||
if (mProgress == null) {
|
||||
return;
|
||||
}
|
||||
if (mProgress.size() == 1) {
|
||||
throw new RuntimeException("Tried to pop progressable without prior push! "
|
||||
+ "This is a programming error, please file a bug report.");
|
||||
}
|
||||
mProgress.pop();
|
||||
}
|
||||
|
||||
private void progress(int message, int current) {
|
||||
if (mProgress == null) {
|
||||
return;
|
||||
}
|
||||
mProgress.peek().setProgress(message, current, 100);
|
||||
}
|
||||
|
||||
/** Creates new secret key. */
|
||||
private PGPKeyPair createKey(int algorithmChoice, int keySize) throws PgpGeneralMsgIdException {
|
||||
private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) {
|
||||
|
||||
try {
|
||||
if (keySize < 512) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
int algorithm;
|
||||
@@ -116,6 +139,7 @@ public class PgpKeyOperation {
|
||||
|
||||
switch (algorithmChoice) {
|
||||
case Constants.choice.algorithm.dsa: {
|
||||
progress(R.string.progress_generating_dsa, 30);
|
||||
keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
algorithm = PGPPublicKey.DSA;
|
||||
@@ -123,6 +147,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
case Constants.choice.algorithm.elgamal: {
|
||||
progress(R.string.progress_generating_elgamal, 30);
|
||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
BigInteger p = Primes.getBestPrime(keySize);
|
||||
BigInteger g = new BigInteger("2");
|
||||
@@ -135,6 +160,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
case Constants.choice.algorithm.rsa: {
|
||||
progress(R.string.progress_generating_rsa, 30);
|
||||
keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(keySize, new SecureRandom());
|
||||
|
||||
@@ -143,7 +169,8 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,31 +184,55 @@ public class PgpKeyOperation {
|
||||
} catch(InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch(PGPException e) {
|
||||
throw new PgpGeneralMsgIdException(R.string.msg_mf_error_pgp, e);
|
||||
Log.e(Constants.TAG, "internal pgp error", e);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public UncachedKeyRing createSecretKeyRing(SaveKeyringParcel saveParcel, OperationLog log,
|
||||
int indent) {
|
||||
public EditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
|
||||
try {
|
||||
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
|
||||
log.add(LogLevel.START, LogType.MSG_CR, indent);
|
||||
progress(R.string.progress_building_key, 0);
|
||||
indent += 1;
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
|
||||
if (saveParcel.addSubKeys == null || saveParcel.addSubKeys.isEmpty()) {
|
||||
if (saveParcel.mAddSubKeys.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.addSubKeys.remove(0);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
|
||||
if (saveParcel.mAddUserIds.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_USER_ID, indent);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.mAddSubKeys.remove(0);
|
||||
if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CERTIFY, indent);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (add.mAlgorithm == Constants.choice.algorithm.elgamal) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
subProgressPush(10, 30);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
subProgressPop();
|
||||
|
||||
// return null if this failed (an error will already have been logged by createKey)
|
||||
if (keyPair == null) {
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
progress(R.string.progress_building_master_key, 40);
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
@@ -195,17 +246,16 @@ public class PgpKeyOperation {
|
||||
PGPSecretKeyRing sKR = new PGPSecretKeyRing(
|
||||
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
|
||||
|
||||
return internal(sKR, masterSecretKey, saveParcel, "", log, indent);
|
||||
subProgressPush(50, 100);
|
||||
return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log);
|
||||
|
||||
} catch (PGPException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
Log.e(Constants.TAG, "pgp error encoding key", e);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "io error encoding key", e);
|
||||
return null;
|
||||
} catch (PgpGeneralMsgIdException e) {
|
||||
Log.e(Constants.TAG, "pgp msg id error", e);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -221,8 +271,11 @@ public class PgpKeyOperation {
|
||||
* are changed by adding new certificates, which implicitly override older certificates.
|
||||
*
|
||||
*/
|
||||
public UncachedKeyRing modifySecretKeyRing(WrappedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
String passphrase, OperationLog log, int indent) {
|
||||
public EditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
String passphrase) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
|
||||
/*
|
||||
* 1. Unlock private key
|
||||
@@ -235,14 +288,15 @@ public class PgpKeyOperation {
|
||||
* 6. If requested, change passphrase
|
||||
*/
|
||||
|
||||
log.add(LogLevel.START, LogType.MSG_MF, indent);
|
||||
log.add(LogLevel.START, LogType.MSG_MF, indent,
|
||||
PgpKeyHelper.convertKeyIdToHex(wsKR.getMasterKeyId()));
|
||||
indent += 1;
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
progress(R.string.progress_building_key, 0);
|
||||
|
||||
// Make sure this is called with a proper SaveKeyringParcel
|
||||
if (saveParcel.mMasterKeyId == null || saveParcel.mMasterKeyId != wsKR.getMasterKeyId()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// We work on bouncycastle object level here
|
||||
@@ -254,22 +308,30 @@ public class PgpKeyOperation {
|
||||
|| !Arrays.equals(saveParcel.mFingerprint,
|
||||
masterSecretKey.getPublicKey().getFingerprint())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_FINGERPRINT, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
return internal(sKR, masterSecretKey, saveParcel, passphrase, log, indent);
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log);
|
||||
|
||||
}
|
||||
|
||||
private UncachedKeyRing internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
int masterKeyFlags,
|
||||
SaveKeyringParcel saveParcel, String passphrase,
|
||||
OperationLog log, int indent) {
|
||||
OperationLog log) {
|
||||
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
int indent = 1;
|
||||
|
||||
progress(R.string.progress_modify, 0);
|
||||
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
|
||||
// 1. Unlock private key
|
||||
progress(R.string.progress_modify_unlock, 10);
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_UNLOCK, indent);
|
||||
PGPPrivateKey masterPrivateKey;
|
||||
{
|
||||
@@ -279,7 +341,7 @@ public class PgpKeyOperation {
|
||||
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,18 +351,28 @@ public class PgpKeyOperation {
|
||||
PGPPublicKey modifiedPublicKey = masterPublicKey;
|
||||
|
||||
// 2a. Add certificates for new user ids
|
||||
for (String userId : saveParcel.addUserIds) {
|
||||
subProgressPush(15, 25);
|
||||
for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_adduid, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
|
||||
String userId = saveParcel.mAddUserIds.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
|
||||
|
||||
if (userId.equals("")) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_UID_ERROR_EMPTY, indent+1);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// this operation supersedes all previous binding and revocation certificates,
|
||||
// so remove those to retain assertions from canonicalization for later operations
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
|
||||
if (it != null) {
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
|
||||
// if it's not a self cert, never mind
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
continue;
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|
||||
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
|
||||
@@ -314,27 +386,39 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// if it's supposed to be primary, we can do that here as well
|
||||
boolean isPrimary = saveParcel.changePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.changePrimaryUserId);
|
||||
boolean isPrimary = saveParcel.mChangePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.mChangePrimaryUserId);
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
|
||||
masterPublicKey, userId, isPrimary);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
|
||||
masterPublicKey, userId, isPrimary, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 2b. Add revocations for revoked user ids
|
||||
for (String userId : saveParcel.revokeUserIds) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
|
||||
// a duplicate revocatin will be removed during canonicalization, so no need to
|
||||
subProgressPush(25, 40);
|
||||
for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_revokeuid, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
|
||||
String userId = saveParcel.mRevokeUserIds.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent, userId);
|
||||
|
||||
// a duplicate revocation will be removed during canonicalization, so no need to
|
||||
// take care of that here.
|
||||
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
|
||||
masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 3. If primary user id changed, generate new certificates for both old and new
|
||||
if (saveParcel.changePrimaryUserId != null) {
|
||||
if (saveParcel.mChangePrimaryUserId != null) {
|
||||
progress(R.string.progress_modify_primaryuid, 40);
|
||||
|
||||
// keep track if we actually changed one
|
||||
boolean ok = false;
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent);
|
||||
indent += 1;
|
||||
|
||||
// we work on the modifiedPublicKey here, to respect new or newly revoked uids
|
||||
// noinspection unchecked
|
||||
@@ -343,10 +427,11 @@ public class PgpKeyOperation {
|
||||
PGPSignature currentCert = null;
|
||||
// noinspection unchecked
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(
|
||||
masterPublicKey.getSignaturesForID(userId))) {
|
||||
// if it's not a self cert, never mind
|
||||
modifiedPublicKey.getSignaturesForID(userId))) {
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
continue;
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
// we know from canonicalization that if there is any revocation here, it
|
||||
// is valid and not superseded by a newer certification.
|
||||
@@ -367,30 +452,33 @@ public class PgpKeyOperation {
|
||||
if (currentCert == null) {
|
||||
// no certificate found?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// we definitely should not update certifications of revoked keys, so just leave it.
|
||||
if (isRevoked) {
|
||||
// revoked user ids cannot be primary!
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is~ the/a primary user id
|
||||
if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {
|
||||
if (currentCert.getHashedSubPackets() != null
|
||||
&& currentCert.getHashedSubPackets().isPrimaryUserID()) {
|
||||
// if it's the one we want, just leave it as is
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
ok = true;
|
||||
continue;
|
||||
}
|
||||
// otherwise, generate new non-primary certification
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, false);
|
||||
masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
continue;
|
||||
@@ -399,20 +487,28 @@ public class PgpKeyOperation {
|
||||
// if we are here, this is not currently a primary user id
|
||||
|
||||
// if it should be
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
// add shiny new primary user id certificate
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, true);
|
||||
masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
ok = true;
|
||||
}
|
||||
|
||||
// user id is not primary and is not supposed to be - nothing to do here.
|
||||
|
||||
}
|
||||
|
||||
indent -= 1;
|
||||
|
||||
if (!ok) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the secret key ring
|
||||
@@ -423,40 +519,80 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// 4a. For each subkey change, generate new subkey binding certificate
|
||||
for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
|
||||
subProgressPush(50, 60);
|
||||
for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeychange, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
|
||||
SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
|
||||
// TODO allow changes in master key? this implies generating new user id certs...
|
||||
if (change.mKeyId == masterPublicKey.getKeyID()) {
|
||||
Log.e(Constants.TAG, "changing the master key not supported");
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
|
||||
if (sKey == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
|
||||
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) {
|
||||
// expiry must not be in the past
|
||||
if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
|
||||
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// generate and add new signature. we can be sloppy here and just leave the old one,
|
||||
// it will be removed during canonicalization
|
||||
// keep old flags, or replace with new ones
|
||||
int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags;
|
||||
long expiry;
|
||||
if (change.mExpiry == null) {
|
||||
long valid = pKey.getValidSeconds();
|
||||
expiry = valid == 0
|
||||
? 0
|
||||
: pKey.getCreationTime().getTime() / 1000 + pKey.getValidSeconds();
|
||||
} else {
|
||||
expiry = change.mExpiry;
|
||||
}
|
||||
|
||||
// drop all old signatures, they will be superseded by the new one
|
||||
//noinspection unchecked
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) {
|
||||
// special case: if there is a revocation, don't use expiry from before
|
||||
if (change.mExpiry == null
|
||||
&& sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) {
|
||||
expiry = 0;
|
||||
}
|
||||
pKey = PGPPublicKey.removeCertification(pKey, sig);
|
||||
}
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
|
||||
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
|
||||
sKey, pKey, flags, expiry, passphrase);
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 4b. For each subkey revocation, generate new subkey revocation certificate
|
||||
for (long revocation : saveParcel.revokeSubKeys) {
|
||||
subProgressPush(60, 70);
|
||||
for (int i = 0; i < saveParcel.mRevokeSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeyrevoke, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
|
||||
long revocation = saveParcel.mRevokeSubKeys.get(i);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(revocation));
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(revocation);
|
||||
if (sKey == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(revocation));
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
@@ -466,54 +602,64 @@ public class PgpKeyOperation {
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 5. Generate and add new subkeys
|
||||
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) {
|
||||
try {
|
||||
subProgressPush(70, 90);
|
||||
for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {
|
||||
|
||||
if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return null;
|
||||
}
|
||||
progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
|
||||
SaveKeyringParcel.SubkeyAdd add = saveParcel.mAddSubKeys.get(0);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
|
||||
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
|
||||
sha1Calc, false, keyEncryptor);
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
|
||||
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
} catch (PgpGeneralMsgIdException e) {
|
||||
return null;
|
||||
if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
subProgressPush(
|
||||
(i-1) * (100 / saveParcel.mAddSubKeys.size()),
|
||||
i * (100 / saveParcel.mAddSubKeys.size())
|
||||
);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
subProgressPop();
|
||||
if(keyPair == null) {
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry == null ? 0 : add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
|
||||
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
|
||||
sha1Calc, false, keyEncryptor);
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
|
||||
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 6. If requested, change passphrase
|
||||
if (saveParcel.newPassphrase != null) {
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
progress(R.string.progress_modify_passphrase, 90);
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
|
||||
.get(HashAlgorithmTags.SHA1);
|
||||
@@ -523,7 +669,7 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.newPassphrase.toCharArray());
|
||||
saveParcel.mNewPassphrase.toCharArray());
|
||||
|
||||
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
|
||||
}
|
||||
@@ -531,22 +677,24 @@ public class PgpKeyOperation {
|
||||
// This one must only be thrown by
|
||||
} catch (IOException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "encountered pgp error while modifying key", e);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (SignatureException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SIG, indent+1);
|
||||
return null;
|
||||
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
progress(R.string.progress_done, 100);
|
||||
log.add(LogLevel.OK, LogType.MSG_MF_SUCCESS, indent);
|
||||
return new UncachedKeyRing(sKR);
|
||||
return new EditKeyResult(OperationResultParcel.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserIdSignature(
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary)
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
pKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
@@ -558,6 +706,7 @@ public class PgpKeyOperation {
|
||||
subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
subHashedPacketsGen.setPrimaryUserID(false, primary);
|
||||
subHashedPacketsGen.setKeyFlags(false, flags);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
return sGen.generateCertification(userId, pKey);
|
||||
@@ -599,7 +748,7 @@ public class PgpKeyOperation {
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase)
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, String passphrase)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
@@ -611,7 +760,7 @@ public class PgpKeyOperation {
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry)
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
|
||||
// date for signing
|
||||
@@ -640,17 +789,9 @@ public class PgpKeyOperation {
|
||||
hashedPacketsGen.setKeyFlags(false, flags);
|
||||
}
|
||||
|
||||
if (expiry != null) {
|
||||
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(pKey.getCreationTime());
|
||||
|
||||
// (Just making sure there's no programming error here, this MUST have been checked above!)
|
||||
if (new Date(expiry).before(todayDate)) {
|
||||
throw new RuntimeException("Bad subkey creation date, this is a bug!");
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
|
||||
} else {
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
if (expiry > 0) {
|
||||
long creationTime = pKey.getCreationTime().getTime() / 1000;
|
||||
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime);
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
@@ -665,4 +806,22 @@ public class PgpKeyOperation {
|
||||
|
||||
}
|
||||
|
||||
/** Returns all flags valid for this key.
|
||||
*
|
||||
* This method does not do any validity checks on the signature, so it should not be used on
|
||||
* a non-canonicalized key!
|
||||
*
|
||||
*/
|
||||
private static int readKeyFlags(PGPPublicKey key) {
|
||||
int flags = 0;
|
||||
//noinspection unchecked
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (sig.getHashedSubPackets() == null) {
|
||||
continue;
|
||||
}
|
||||
flags |= sig.getHashedSubPackets().getKeyFlags();
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -266,11 +266,11 @@ public class PgpSignEncrypt {
|
||||
}
|
||||
|
||||
/* Get keys for signature generation for later usage */
|
||||
WrappedSecretKey signingKey = null;
|
||||
CanonicalizedSecretKey signingKey = null;
|
||||
if (enableSignature) {
|
||||
WrappedSecretKeyRing signingKeyRing;
|
||||
CanonicalizedSecretKeyRing signingKeyRing;
|
||||
try {
|
||||
signingKeyRing = mProviderHelper.getWrappedSecretKeyRing(mSignatureMasterKeyId);
|
||||
signingKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(mSignatureMasterKeyId);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
throw new NoSigningKeyException();
|
||||
}
|
||||
@@ -316,9 +316,9 @@ public class PgpSignEncrypt {
|
||||
// Asymmetric encryption
|
||||
for (long id : mEncryptionMasterKeyIds) {
|
||||
try {
|
||||
WrappedPublicKeyRing keyRing = mProviderHelper.getWrappedPublicKeyRing(
|
||||
CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(id));
|
||||
WrappedPublicKey key = keyRing.getEncryptionSubKey();
|
||||
CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
|
||||
cPk.addMethod(key.getPubKeyEncryptionGenerator());
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPKeyFlags;
|
||||
@@ -14,6 +13,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
@@ -22,20 +22,18 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Vector;
|
||||
|
||||
/** Wrapper around PGPKeyRing class, to be constructed from bytes.
|
||||
*
|
||||
@@ -49,7 +47,7 @@ import java.util.Vector;
|
||||
* treated equally for most purposes in UI code. It is up to the programmer to
|
||||
* take care of the differences.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.WrappedKeyRing
|
||||
* @see CanonicalizedKeyRing
|
||||
* @see org.sufficientlysecure.keychain.pgp.UncachedPublicKey
|
||||
* @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey
|
||||
*
|
||||
@@ -59,18 +57,10 @@ public class UncachedKeyRing {
|
||||
|
||||
final PGPKeyRing mRing;
|
||||
final boolean mIsSecret;
|
||||
final boolean mIsCanonicalized;
|
||||
|
||||
UncachedKeyRing(PGPKeyRing ring) {
|
||||
mRing = ring;
|
||||
mIsSecret = ring instanceof PGPSecretKeyRing;
|
||||
mIsCanonicalized = false;
|
||||
}
|
||||
|
||||
private UncachedKeyRing(PGPKeyRing ring, boolean canonicalized) {
|
||||
mRing = ring;
|
||||
mIsSecret = ring instanceof PGPSecretKeyRing;
|
||||
mIsCanonicalized = canonicalized;
|
||||
}
|
||||
|
||||
public long getMasterKeyId() {
|
||||
@@ -81,11 +71,15 @@ public class UncachedKeyRing {
|
||||
return new UncachedPublicKey(mRing.getPublicKey());
|
||||
}
|
||||
|
||||
public UncachedPublicKey getPublicKey(long keyId) {
|
||||
return new UncachedPublicKey(mRing.getPublicKey(keyId));
|
||||
}
|
||||
|
||||
public Iterator<UncachedPublicKey> getPublicKeys() {
|
||||
final Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
return new Iterator<UncachedPublicKey>() {
|
||||
public void remove() {
|
||||
it.remove();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public UncachedPublicKey next() {
|
||||
return new UncachedPublicKey(it.next());
|
||||
@@ -101,10 +95,6 @@ public class UncachedKeyRing {
|
||||
return mIsSecret;
|
||||
}
|
||||
|
||||
public boolean isCanonicalized() {
|
||||
return mIsCanonicalized;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return mRing.getEncoded();
|
||||
}
|
||||
@@ -115,43 +105,86 @@ public class UncachedKeyRing {
|
||||
|
||||
public static UncachedKeyRing decodeFromData(byte[] data)
|
||||
throws PgpGeneralException, IOException {
|
||||
BufferedInputStream bufferedInput =
|
||||
new BufferedInputStream(new ByteArrayInputStream(data));
|
||||
if (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 && obj instanceof PGPKeyRing) {
|
||||
return new UncachedKeyRing((PGPKeyRing) obj);
|
||||
} else {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
} else {
|
||||
Iterator<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data));
|
||||
|
||||
if ( ! parsed.hasNext()) {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
|
||||
UncachedKeyRing ring = parsed.next();
|
||||
|
||||
if (parsed.hasNext()) {
|
||||
throw new PgpGeneralException("Expected single keyring in stream, found at least two");
|
||||
}
|
||||
|
||||
return ring;
|
||||
|
||||
}
|
||||
|
||||
public static List<UncachedKeyRing> fromStream(InputStream stream)
|
||||
throws PgpGeneralException, IOException {
|
||||
public static Iterator<UncachedKeyRing> fromStream(final InputStream stream) throws IOException {
|
||||
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
|
||||
return new Iterator<UncachedKeyRing>() {
|
||||
|
||||
List<UncachedKeyRing> result = new Vector<UncachedKeyRing>();
|
||||
UncachedKeyRing mNext = null;
|
||||
PGPObjectFactory mObjectFactory = null;
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
private void cacheNext() {
|
||||
if (mNext != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
while(stream.available() > 0) {
|
||||
// if there are no objects left from the last factory, create a new one
|
||||
if (mObjectFactory == null) {
|
||||
mObjectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
|
||||
}
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = mObjectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
if (!(obj instanceof PGPKeyRing)) {
|
||||
Log.i(Constants.TAG,
|
||||
"Skipping object of bad type " + obj.getClass().getName() + " in stream");
|
||||
// skip object
|
||||
continue;
|
||||
}
|
||||
mNext = new UncachedKeyRing((PGPKeyRing) obj);
|
||||
return;
|
||||
}
|
||||
// if we are past the while loop, that means the objectFactory had no next
|
||||
mObjectFactory = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException while processing stream", e);
|
||||
}
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
result.add(new UncachedKeyRing((PGPKeyRing) obj));
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
cacheNext();
|
||||
return mNext != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UncachedKeyRing next() {
|
||||
try {
|
||||
cacheNext();
|
||||
return mNext;
|
||||
} finally {
|
||||
mNext = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public void encodeArmored(OutputStream out, String version) throws IOException {
|
||||
@@ -161,25 +194,6 @@ public class UncachedKeyRing {
|
||||
aos.close();
|
||||
}
|
||||
|
||||
public HashSet<Long> getAvailableSubkeys() {
|
||||
if(!isSecret()) {
|
||||
throw new RuntimeException("Tried to find available subkeys from non-secret keys. " +
|
||||
"This is a programming error and should never happen!");
|
||||
}
|
||||
|
||||
HashSet<Long> result = new HashSet<Long>();
|
||||
// then, mark exactly the keys we have available
|
||||
for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(
|
||||
((PGPSecretKeyRing) mRing).getSecretKeys())) {
|
||||
S2K s2k = sub.getS2K();
|
||||
// Set to 1, except if the encryption type is GNU_DUMMY_S2K
|
||||
if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) {
|
||||
result.add(sub.getKeyID());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be
|
||||
* applied to public keyrings only.
|
||||
*
|
||||
@@ -189,7 +203,7 @@ public class UncachedKeyRing {
|
||||
* - Remove all certificates flagged as "local"
|
||||
* - Remove all certificates which are superseded by a newer one on the same target,
|
||||
* including revocations with later re-certifications.
|
||||
* - Remove all certificates of unknown type:
|
||||
* - Remove all certificates in other positions if not of known type:
|
||||
* - key revocation signatures on the master key
|
||||
* - subkey binding signatures for subkeys
|
||||
* - certifications and certification revocations for user ids
|
||||
@@ -204,7 +218,7 @@ public class UncachedKeyRing {
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public UncachedKeyRing canonicalize(OperationLog log, int indent) {
|
||||
public CanonicalizedKeyRing canonicalize(OperationLog log, int indent) {
|
||||
|
||||
log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()));
|
||||
@@ -225,7 +239,7 @@ public class UncachedKeyRing {
|
||||
|
||||
PGPPublicKey modified = masterKey;
|
||||
PGPSignature revocation = null;
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getSignatures())) {
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getKeySignatures())) {
|
||||
int type = zert.getSignatureType();
|
||||
|
||||
// Disregard certifications on user ids, we will deal with those later
|
||||
@@ -234,6 +248,10 @@ public class UncachedKeyRing {
|
||||
|| type == PGPSignature.CASUAL_CERTIFICATION
|
||||
|| type == PGPSignature.POSITIVE_CERTIFICATION
|
||||
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
// These should not be here...
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE_UID, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
WrappedSignature cert = new WrappedSignature(zert);
|
||||
@@ -317,7 +335,7 @@ public class UncachedKeyRing {
|
||||
|
||||
if (cert.getCreationTime().after(now)) {
|
||||
// Creation date in the future? No way!
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, indent);
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TIME, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
@@ -325,7 +343,7 @@ public class UncachedKeyRing {
|
||||
|
||||
if (cert.isLocal()) {
|
||||
// Creation date in the future? No way!
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, indent);
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_LOCAL, indent);
|
||||
modified = PGPPublicKey.removeCertification(modified, zert);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
@@ -425,7 +443,7 @@ public class UncachedKeyRing {
|
||||
// If no valid certificate (if only a revocation) remains, drop it
|
||||
if (selfCert == null && revocation == null) {
|
||||
modified = PGPPublicKey.removeCertification(modified, userId);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REVOKE_DUP,
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REMOVE,
|
||||
indent, userId);
|
||||
}
|
||||
}
|
||||
@@ -505,14 +523,17 @@ public class UncachedKeyRing {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
|
||||
// if this certificate says it allows signing for the key
|
||||
if (zert.getHashedSubPackets() != null &&
|
||||
zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
|
||||
|
||||
int flags = ((KeyFlags) zert.getHashedSubPackets()
|
||||
.getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
|
||||
// If this subkey is allowed to sign data,
|
||||
if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) {
|
||||
boolean ok = false;
|
||||
// it MUST have an embedded primary key binding signature
|
||||
try {
|
||||
PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
boolean ok = false;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
WrappedSignature subsig = new WrappedSignature(list.get(i));
|
||||
if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
|
||||
@@ -526,17 +547,19 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
// if it doesn't, get rid of this!
|
||||
if (!ok) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if we already have a cert, and this one is not newer: skip it
|
||||
@@ -549,6 +572,8 @@ public class UncachedKeyRing {
|
||||
selfCert = zert;
|
||||
// if this is newer than a possibly existing revocation, drop that one
|
||||
if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_REVOKE_DUP, indent);
|
||||
redundantCerts += 1;
|
||||
revocation = null;
|
||||
}
|
||||
|
||||
@@ -558,7 +583,7 @@ public class UncachedKeyRing {
|
||||
// make sure the certificate checks out
|
||||
try {
|
||||
cert.init(masterKey);
|
||||
if (!cert.verifySignature(key)) {
|
||||
if (!cert.verifySignature(masterKey, key)) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
@@ -582,7 +607,7 @@ public class UncachedKeyRing {
|
||||
|
||||
// it is not properly bound? error!
|
||||
if (selfCert == null) {
|
||||
ring = replacePublicKey(ring, modified);
|
||||
ring = removeSubKey(ring, key);
|
||||
|
||||
log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(key.getKeyID()));
|
||||
@@ -615,7 +640,8 @@ public class UncachedKeyRing {
|
||||
log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, indent);
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(ring, true);
|
||||
return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, 1)
|
||||
: new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, 0);
|
||||
}
|
||||
|
||||
/** This operation merges information from a different keyring, returning a combined
|
||||
@@ -650,7 +676,7 @@ public class UncachedKeyRing {
|
||||
return left.length - right.length;
|
||||
}
|
||||
// compare byte-by-byte
|
||||
for (int i = 0; i < left.length && i < right.length; i++) {
|
||||
for (int i = 0; i < left.length; i++) {
|
||||
if (left[i] != right[i]) {
|
||||
return (left[i] & 0xff) - (right[i] & 0xff);
|
||||
}
|
||||
@@ -678,7 +704,14 @@ public class UncachedKeyRing {
|
||||
final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID());
|
||||
if (resultKey == null) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, indent);
|
||||
result = replacePublicKey(result, key);
|
||||
// special case: if both rings are secret, copy over the secret key
|
||||
if (isSecret() && other.isSecret()) {
|
||||
PGPSecretKey sKey = ((PGPSecretKeyRing) candidate).getSecretKey(key.getKeyID());
|
||||
result = PGPSecretKeyRing.insertSecretKey((PGPSecretKeyRing) result, sKey);
|
||||
} else {
|
||||
// otherwise, just insert the public key
|
||||
result = replacePublicKey(result, key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -686,17 +719,7 @@ public class UncachedKeyRing {
|
||||
PGPPublicKey modified = resultKey;
|
||||
|
||||
// Iterate certifications
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
int type = cert.getSignatureType();
|
||||
// Disregard certifications on user ids, we will deal with those later
|
||||
if (type == PGPSignature.NO_CERTIFICATION
|
||||
|| type == PGPSignature.DEFAULT_CERTIFICATION
|
||||
|| type == PGPSignature.CASUAL_CERTIFICATION
|
||||
|| type == PGPSignature.POSITIVE_CERTIFICATION
|
||||
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getKeySignatures())) {
|
||||
// Don't merge foreign stuff into secret keys
|
||||
if (cert.getKeyID() != masterKeyId && isSecret()) {
|
||||
continue;
|
||||
@@ -744,8 +767,12 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW,
|
||||
indent, Integer.toString(newCerts));
|
||||
if (newCerts > 0) {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, indent,
|
||||
Integer.toString(newCerts));
|
||||
} else {
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MG_UNCHANGED, indent);
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(result);
|
||||
|
||||
@@ -756,19 +783,20 @@ public class UncachedKeyRing {
|
||||
|
||||
}
|
||||
|
||||
public UncachedKeyRing extractPublicKeyRing() {
|
||||
public UncachedKeyRing extractPublicKeyRing() throws IOException {
|
||||
if(!isSecret()) {
|
||||
throw new RuntimeException("Tried to extract public keyring from non-secret keyring. " +
|
||||
"This is a programming error and should never happen!");
|
||||
}
|
||||
|
||||
ArrayList<PGPPublicKey> keys = new ArrayList();
|
||||
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(2048);
|
||||
while (it.hasNext()) {
|
||||
keys.add(it.next());
|
||||
stream.write(it.next().getEncoded());
|
||||
}
|
||||
|
||||
return new UncachedKeyRing(new PGPPublicKeyRing(keys));
|
||||
return new UncachedKeyRing(
|
||||
new PGPPublicKeyRing(stream.toByteArray(), new JcaKeyFingerprintCalculator()));
|
||||
}
|
||||
|
||||
/** This method replaces a public key in a keyring.
|
||||
@@ -792,4 +820,20 @@ public class UncachedKeyRing {
|
||||
return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
|
||||
}
|
||||
|
||||
/** This method removes a subkey in a keyring.
|
||||
*
|
||||
* This method essentially wraps PGP*KeyRing.remove*Key, where the keyring may be of either
|
||||
* the secret or public subclass.
|
||||
*
|
||||
* @return the resulting PGPKeyRing of the same type as the input
|
||||
*/
|
||||
private static PGPKeyRing removeSubKey(PGPKeyRing ring, PGPPublicKey key) {
|
||||
if (ring instanceof PGPPublicKeyRing) {
|
||||
return PGPPublicKeyRing.removePublicKey((PGPPublicKeyRing) ring, key);
|
||||
} else {
|
||||
PGPSecretKey sKey = ((PGPSecretKeyRing) ring).getSecretKey(key.getKeyID());
|
||||
return PGPSecretKeyRing.removeSecretKey((PGPSecretKeyRing) ring, sKey);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -44,14 +42,19 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
Date creationDate = getCreationTime();
|
||||
if (mPublicKey.getValidDays() == 0) {
|
||||
long seconds = mPublicKey.getValidSeconds();
|
||||
if (seconds > Integer.MAX_VALUE) {
|
||||
Log.e(Constants.TAG, "error, expiry time too large");
|
||||
return null;
|
||||
}
|
||||
if (seconds == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Date creationDate = getCreationTime();
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.DATE, mPublicKey.getValidDays());
|
||||
calendar.add(Calendar.SECOND, (int) seconds);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
@@ -77,26 +80,76 @@ public class UncachedPublicKey {
|
||||
return mPublicKey.getBitStrength();
|
||||
}
|
||||
|
||||
/** Returns the primary user id, as indicated by the public key's self certificates.
|
||||
*
|
||||
* This is an expensive operation, since potentially a lot of certificates (and revocations)
|
||||
* have to be checked, and even then the result is NOT guaranteed to be constant through a
|
||||
* canonicalization operation.
|
||||
*
|
||||
* Returns null if there is no primary user id (as indicated by certificates)
|
||||
*
|
||||
*/
|
||||
public String getPrimaryUserId() {
|
||||
String found = null;
|
||||
PGPSignature foundSig = null;
|
||||
for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) {
|
||||
PGPSignature revocation = null;
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignaturesForID(userId))) {
|
||||
if (sig.getHashedSubPackets() != null
|
||||
&& sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) {
|
||||
try {
|
||||
try {
|
||||
|
||||
// if this is a revocation, this is not the user id
|
||||
if (sig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
// make sure it's actually valid
|
||||
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
|
||||
if (!sig.verifyCertification(userId, mPublicKey)) {
|
||||
continue;
|
||||
}
|
||||
if (found != null && found.equals(userId)) {
|
||||
found = null;
|
||||
}
|
||||
revocation = sig;
|
||||
// this revocation may still be overridden by a newer cert
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sig.getHashedSubPackets() != null && sig.getHashedSubPackets().isPrimaryUserID()) {
|
||||
if (foundSig != null && sig.getCreationTime().before(foundSig.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
// ignore if there is a newer revocation for this user id
|
||||
if (revocation != null && sig.getCreationTime().before(revocation.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
// make sure it's actually valid
|
||||
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
|
||||
if (sig.verifyCertification(userId, mPublicKey)) {
|
||||
return userId;
|
||||
found = userId;
|
||||
foundSig = sig;
|
||||
// this one can't be relevant anymore at this point
|
||||
revocation = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// nothing bad happens, the key is just not considered the primary key id
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// nothing bad happens, the key is just not considered the primary key id
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns primary user id if existing. If not, return first encountered user id.
|
||||
*/
|
||||
public String getPrimaryUserIdWithFallback() {
|
||||
String userId = getPrimaryUserId();
|
||||
if (userId == null) {
|
||||
userId = (String) mPublicKey.getUserIDs().next();
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public ArrayList<String> getUnorderedUserIds() {
|
||||
@@ -186,6 +239,21 @@ public class UncachedPublicKey {
|
||||
return mPublicKey;
|
||||
}
|
||||
|
||||
public Iterator<WrappedSignature> getSignatures() {
|
||||
final Iterator<PGPSignature> it = mPublicKey.getSignatures();
|
||||
return new Iterator<WrappedSignature>() {
|
||||
public void remove() {
|
||||
it.remove();
|
||||
}
|
||||
public WrappedSignature next() {
|
||||
return new WrappedSignature(it.next());
|
||||
}
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Iterator<WrappedSignature> getSignaturesForId(String userId) {
|
||||
final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId);
|
||||
return new Iterator<WrappedSignature>() {
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
/** OpenKeychain wrapper around PGPSignature objects.
|
||||
@@ -55,12 +56,41 @@ public class WrappedSignature {
|
||||
return mSig.getCreationTime();
|
||||
}
|
||||
|
||||
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
|
||||
ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>();
|
||||
if (!mSig.hasSubpackets()) {
|
||||
return sigs;
|
||||
}
|
||||
try {
|
||||
PGPSignatureList list;
|
||||
if (mSig.getHashedSubPackets() != null) {
|
||||
list = mSig.getHashedSubPackets().getEmbeddedSignatures();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
}
|
||||
if (mSig.getUnhashedSubPackets() != null) {
|
||||
list = mSig.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return mSig.getEncoded();
|
||||
}
|
||||
|
||||
public boolean isRevocation() {
|
||||
return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON);
|
||||
return mSig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION;
|
||||
}
|
||||
|
||||
public boolean isPrimaryUserId() {
|
||||
@@ -71,6 +101,9 @@ public class WrappedSignature {
|
||||
if(!isRevocation()) {
|
||||
throw new PgpGeneralException("Not a revocation signature.");
|
||||
}
|
||||
if (mSig.getHashedSubPackets() == null) {
|
||||
return null;
|
||||
}
|
||||
SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(
|
||||
SignatureSubpacketTags.REVOCATION_REASON);
|
||||
// For some reason, this is missing in SignatureSubpacketInputStream:146
|
||||
@@ -80,7 +113,7 @@ public class WrappedSignature {
|
||||
return ((RevocationReason) p).getRevocationDescription();
|
||||
}
|
||||
|
||||
public void init(WrappedPublicKey key) throws PgpGeneralException {
|
||||
public void init(CanonicalizedPublicKey key) throws PgpGeneralException {
|
||||
init(key.getPublicKey());
|
||||
}
|
||||
|
||||
@@ -158,7 +191,7 @@ public class WrappedSignature {
|
||||
public boolean verifySignature(UncachedPublicKey key, String uid) throws PgpGeneralException {
|
||||
return verifySignature(key.getPublicKey(), uid);
|
||||
}
|
||||
public boolean verifySignature(WrappedPublicKey key, String uid) throws PgpGeneralException {
|
||||
public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException {
|
||||
return verifySignature(key.getPublicKey(), uid);
|
||||
}
|
||||
|
||||
@@ -179,7 +212,7 @@ public class WrappedSignature {
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
if (!mSig.hasSubpackets()
|
||||
if (mSig.getHashedSubPackets() == null
|
||||
|| !mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrimaryUserIdWithFallback() throws PgpGeneralException {
|
||||
return getPrimaryUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRevoked() throws PgpGeneralException {
|
||||
try {
|
||||
|
||||
@@ -28,10 +28,12 @@ import android.os.RemoteException;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
@@ -39,8 +41,6 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
@@ -180,7 +180,7 @@ public class ProviderHelper {
|
||||
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
|
||||
}
|
||||
|
||||
private LongSparseArray<WrappedPublicKey> getTrustedMasterKeys() {
|
||||
private LongSparseArray<CanonicalizedPublicKey> getTrustedMasterKeys() {
|
||||
Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] {
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
// we pick from cache only information that is not easily available from keyrings
|
||||
@@ -190,16 +190,15 @@ public class ProviderHelper {
|
||||
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
|
||||
|
||||
try {
|
||||
LongSparseArray<WrappedPublicKey> result = new LongSparseArray<WrappedPublicKey>();
|
||||
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<CanonicalizedPublicKey>();
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) do {
|
||||
long masterKeyId = cursor.getLong(0);
|
||||
boolean hasAnySecret = cursor.getInt(1) > 0;
|
||||
int verified = cursor.getInt(2);
|
||||
byte[] blob = cursor.getBlob(3);
|
||||
if (blob != null) {
|
||||
result.put(masterKeyId,
|
||||
new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey());
|
||||
new CanonicalizedPublicKeyRing(blob, verified).getPublicKey());
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
@@ -217,23 +216,23 @@ public class ProviderHelper {
|
||||
return new CachedPublicKeyRing(this, queryUri);
|
||||
}
|
||||
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(long id) throws NotFoundException {
|
||||
return (WrappedPublicKeyRing) getWrappedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), false);
|
||||
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long id) throws NotFoundException {
|
||||
return (CanonicalizedPublicKeyRing) getCanonicalizedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), false);
|
||||
}
|
||||
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (WrappedPublicKeyRing) getWrappedKeyRing(queryUri, false);
|
||||
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (CanonicalizedPublicKeyRing) getCanonicalizedKeyRing(queryUri, false);
|
||||
}
|
||||
|
||||
public WrappedSecretKeyRing getWrappedSecretKeyRing(long id) throws NotFoundException {
|
||||
return (WrappedSecretKeyRing) getWrappedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), true);
|
||||
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long id) throws NotFoundException {
|
||||
return (CanonicalizedSecretKeyRing) getCanonicalizedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), true);
|
||||
}
|
||||
|
||||
public WrappedSecretKeyRing getWrappedSecretKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (WrappedSecretKeyRing) getWrappedKeyRing(queryUri, true);
|
||||
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri queryUri) throws NotFoundException {
|
||||
return (CanonicalizedSecretKeyRing) getCanonicalizedKeyRing(queryUri, true);
|
||||
}
|
||||
|
||||
private KeyRing getWrappedKeyRing(Uri queryUri, boolean secret) throws NotFoundException {
|
||||
private KeyRing getCanonicalizedKeyRing(Uri queryUri, boolean secret) throws NotFoundException {
|
||||
Cursor cursor = mContentResolver.query(queryUri,
|
||||
new String[]{
|
||||
// we pick from cache only information that is not easily available from keyrings
|
||||
@@ -252,8 +251,8 @@ public class ProviderHelper {
|
||||
throw new NotFoundException("Secret key not available!");
|
||||
}
|
||||
return secret
|
||||
? new WrappedSecretKeyRing(blob, true, verified)
|
||||
: new WrappedPublicKeyRing(blob, hasAnySecret, verified);
|
||||
? new CanonicalizedSecretKeyRing(blob, true, verified)
|
||||
: new CanonicalizedPublicKeyRing(blob, verified);
|
||||
} else {
|
||||
throw new NotFoundException("Key not found!");
|
||||
}
|
||||
@@ -271,16 +270,8 @@ public class ProviderHelper {
|
||||
* and need to be saved externally to be preserved past the operation.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private int internalSavePublicKeyRing(UncachedKeyRing keyRing,
|
||||
Progressable progress, boolean selfCertsAreTrusted) {
|
||||
if (keyRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
if (!keyRing.isCanonicalized()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
private int saveCanonicalizedPublicKeyRing(CanonicalizedPublicKeyRing keyRing,
|
||||
Progressable progress, boolean selfCertsAreTrusted) {
|
||||
|
||||
// start with ok result
|
||||
int result = SaveKeyringResult.SAVED_PUBLIC;
|
||||
@@ -318,7 +309,7 @@ public class ProviderHelper {
|
||||
{ // insert subkeys
|
||||
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
|
||||
int rank = 0;
|
||||
for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
|
||||
for (CanonicalizedPublicKey key : keyRing.publicKeyIterator()) {
|
||||
long keyId = key.getKeyId();
|
||||
log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER : LogType.MSG_IP_SUBKEY,
|
||||
PgpKeyHelper.convertKeyIdToHex(keyId)
|
||||
@@ -401,7 +392,7 @@ public class ProviderHelper {
|
||||
mIndent -= 1;
|
||||
|
||||
// get a list of owned secret keys, for verification filtering
|
||||
LongSparseArray<WrappedPublicKey> trustedKeys = getTrustedMasterKeys();
|
||||
LongSparseArray<CanonicalizedPublicKey> trustedKeys = getTrustedMasterKeys();
|
||||
|
||||
// classify and order user ids. primary are moved to the front, revoked to the back,
|
||||
// otherwise the order in the keyfile is preserved.
|
||||
@@ -445,13 +436,12 @@ public class ProviderHelper {
|
||||
|
||||
// verify signatures from known private keys
|
||||
if (trustedKeys.indexOfKey(certId) >= 0) {
|
||||
WrappedPublicKey trustedKey = trustedKeys.get(certId);
|
||||
CanonicalizedPublicKey trustedKey = trustedKeys.get(certId);
|
||||
cert.init(trustedKey);
|
||||
if (cert.verifySignature(masterKey, userId)) {
|
||||
item.trustedCerts.add(cert);
|
||||
log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD,
|
||||
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()),
|
||||
trustedKey.getPrimaryUserId()
|
||||
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId())
|
||||
);
|
||||
} else {
|
||||
log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD);
|
||||
@@ -560,28 +550,13 @@ public class ProviderHelper {
|
||||
/** Saves an UncachedKeyRing of the secret variant into the db.
|
||||
* This method will fail if no corresponding public keyring is in the database!
|
||||
*/
|
||||
private int internalSaveSecretKeyRing(UncachedKeyRing keyRing) {
|
||||
|
||||
if (!keyRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (!keyRing.isCanonicalized()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
private int saveCanonicalizedSecretKeyRing(CanonicalizedSecretKeyRing keyRing) {
|
||||
|
||||
long masterKeyId = keyRing.getMasterKeyId();
|
||||
log(LogLevel.START, LogType.MSG_IS, PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
mIndent += 1;
|
||||
try {
|
||||
|
||||
// Canonicalize this key, to assert a number of assumptions made about it.
|
||||
keyRing = keyRing.canonicalize(mLog, mIndent);
|
||||
if (keyRing == null) {
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
try {
|
||||
|
||||
// IF this is successful, it's a secret key
|
||||
int result = SaveKeyringResult.SAVED_SECRET;
|
||||
@@ -616,8 +591,7 @@ public class ProviderHelper {
|
||||
log(LogLevel.INFO, LogType.MSG_IS_IMPORTING_SUBKEYS);
|
||||
mIndent += 1;
|
||||
Set<Long> available = keyRing.getAvailableSubkeys();
|
||||
for (UncachedPublicKey sub :
|
||||
new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
|
||||
for (UncachedPublicKey sub : keyRing.publicKeyIterator()) {
|
||||
long id = sub.getKeyId();
|
||||
if (available.contains(id)) {
|
||||
int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?",
|
||||
@@ -668,9 +642,16 @@ public class ProviderHelper {
|
||||
log(LogLevel.START, LogType.MSG_IP, PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
mIndent += 1;
|
||||
|
||||
if (publicRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
CanonicalizedPublicKeyRing canPublicRing;
|
||||
|
||||
// If there is an old keyring, merge it
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into the old one
|
||||
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
|
||||
@@ -681,8 +662,8 @@ public class ProviderHelper {
|
||||
}
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
publicRing = publicRing.canonicalize(mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
@@ -696,39 +677,40 @@ public class ProviderHelper {
|
||||
// Not an issue, just means we are dealing with a new keyring.
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
publicRing = publicRing.canonicalize(mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If there is a secret key, merge new data (if any) and save the key for later
|
||||
UncachedKeyRing secretRing;
|
||||
CanonicalizedSecretKeyRing canSecretRing;
|
||||
try {
|
||||
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached();
|
||||
UncachedKeyRing secretRing = getCanonicalizedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
secretRing = secretRing.merge(publicRing, mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
secretRing = secretRing.canonicalize(mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
// This has always been a secret key ring, this is a safe cast
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
// No secret key available (this is what happens most of the time)
|
||||
secretRing = null;
|
||||
canSecretRing = null;
|
||||
}
|
||||
|
||||
int result = internalSavePublicKeyRing(publicRing, progress, secretRing != null);
|
||||
int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
|
||||
|
||||
// Save the saved keyring (if any)
|
||||
if (secretRing != null) {
|
||||
if (canSecretRing != null) {
|
||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||
int secretResult = internalSaveSecretKeyRing(secretRing);
|
||||
int secretResult = saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
|
||||
result |= SaveKeyringResult.SAVED_SECRET;
|
||||
}
|
||||
@@ -752,12 +734,19 @@ public class ProviderHelper {
|
||||
log(LogLevel.START, LogType.MSG_IS, PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
mIndent += 1;
|
||||
|
||||
if ( ! secretRing.isSecret()) {
|
||||
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
CanonicalizedSecretKeyRing canSecretRing;
|
||||
|
||||
// If there is an old secret key, merge it.
|
||||
try {
|
||||
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldSecretRing = getCanonicalizedSecretKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new secret ring into old one
|
||||
secretRing = oldSecretRing.merge(secretRing, mLog, mIndent);
|
||||
secretRing = secretRing.merge(oldSecretRing, mLog, mIndent);
|
||||
|
||||
// If this is null, there is an error in the log so we can just return
|
||||
if (secretRing == null) {
|
||||
@@ -765,8 +754,9 @@ public class ProviderHelper {
|
||||
}
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
secretRing = secretRing.canonicalize(mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
// This is a safe cast, because we made sure this is a secret ring above
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
@@ -781,8 +771,9 @@ public class ProviderHelper {
|
||||
// Not an issue, just means we are dealing with a new keyring
|
||||
|
||||
// Canonicalize this keyring, to assert a number of assumptions made about it.
|
||||
secretRing = secretRing.canonicalize(mLog, mIndent);
|
||||
if (secretRing == null) {
|
||||
// This is a safe cast, because we made sure this is a secret ring above
|
||||
canSecretRing = (CanonicalizedSecretKeyRing) secretRing.canonicalize(mLog, mIndent);
|
||||
if (canSecretRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
@@ -791,39 +782,34 @@ public class ProviderHelper {
|
||||
// Merge new data into public keyring as well, if there is any
|
||||
UncachedKeyRing publicRing;
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldPublicRing = getCanonicalizedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
// Merge data from new secret ring into public one
|
||||
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
// If nothing changed, never mind
|
||||
if (Arrays.hashCode(publicRing.getEncoded())
|
||||
== Arrays.hashCode(oldPublicRing.getEncoded())) {
|
||||
publicRing = null;
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log(LogLevel.DEBUG, LogType.MSG_IS_PUBRING_GENERATE);
|
||||
publicRing = secretRing.extractPublicKeyRing();
|
||||
}
|
||||
|
||||
if (publicRing != null) {
|
||||
publicRing = publicRing.canonicalize(mLog, mIndent);
|
||||
if (publicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
CanonicalizedPublicKeyRing canPublicRing = (CanonicalizedPublicKeyRing) publicRing.canonicalize(mLog, mIndent);
|
||||
if (canPublicRing == null) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
int result = internalSavePublicKeyRing(publicRing, progress, true);
|
||||
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
int result;
|
||||
|
||||
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
|
||||
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
|
||||
}
|
||||
|
||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||
int result = internalSaveSecretKeyRing(secretRing);
|
||||
result = saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||
|
||||
return new SaveKeyringResult(result, mLog);
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -53,6 +53,12 @@ import java.util.Set;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
|
||||
static final String[] KEYRING_PROJECTION =
|
||||
new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
};
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
@@ -70,7 +76,7 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
for (String email : encryptionUserIds) {
|
||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
|
||||
Cursor cursor = getContentResolver().query(uri, KEYRING_PROJECTION, null, null, null);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
long id = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
@@ -419,7 +425,7 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
try {
|
||||
// try to find key, throws NotFoundException if not in db!
|
||||
mProviderHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
|
||||
@@ -32,10 +32,11 @@ import android.widget.Button;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.EditKeyActivityOld;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
import org.sufficientlysecure.keychain.util.AlgorithmNames;
|
||||
@@ -163,11 +164,11 @@ public class AccountSettingsFragment extends Fragment implements
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(getActivity(), EditKeyActivityOld.class);
|
||||
intent.setAction(EditKeyActivityOld.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
// set default user id to account name
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_USER_IDS, mAccSettings.getAccountName());
|
||||
String[] userId = KeyRing.splitUserId(mAccSettings.getAccountName());
|
||||
|
||||
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NAME, userId[0]);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userId[1]);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_KEY);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,15 @@ 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.keyimport.FileImportCache;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@@ -44,15 +48,13 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@@ -138,9 +140,6 @@ public class KeychainIntentService extends IntentService
|
||||
// delete file securely
|
||||
public static final String DELETE_FILE = "deleteFile";
|
||||
|
||||
// import key
|
||||
public static final String IMPORT_KEY_LIST = "import_key_list";
|
||||
|
||||
// export key
|
||||
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
||||
public static final String EXPORT_FILENAME = "export_filename";
|
||||
@@ -175,7 +174,7 @@ public class KeychainIntentService extends IntentService
|
||||
// export
|
||||
public static final String RESULT_EXPORT = "exported";
|
||||
|
||||
public static final String RESULT = "result";
|
||||
public static final String RESULT_IMPORT = "result";
|
||||
|
||||
Messenger mMessenger;
|
||||
|
||||
@@ -342,39 +341,37 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
/* Operation */
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 50, 100));
|
||||
try {
|
||||
OperationLog log = new OperationLog();
|
||||
UncachedKeyRing ring;
|
||||
if (saveParcel.mMasterKeyId != null) {
|
||||
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
|
||||
WrappedSecretKeyRing secRing =
|
||||
providerHelper.getWrappedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 60, 100));
|
||||
EditKeyResult result;
|
||||
|
||||
ring = keyOperations.modifySecretKeyRing(secRing, saveParcel,
|
||||
passphrase, log, 0);
|
||||
} else {
|
||||
ring = keyOperations.createSecretKeyRing(saveParcel, log, 0);
|
||||
}
|
||||
if (saveParcel.mMasterKeyId != null) {
|
||||
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
|
||||
CanonicalizedSecretKeyRing secRing =
|
||||
providerHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));
|
||||
result = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
|
||||
} else {
|
||||
result = keyOperations.createSecretKeyRing(saveParcel);
|
||||
}
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.newPassphrase != null) {
|
||||
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
|
||||
saveParcel.newPassphrase);
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
sendErrorToHandler(e);
|
||||
UncachedKeyRing ring = result.getRing();
|
||||
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100));
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
|
||||
saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback());
|
||||
}
|
||||
|
||||
setProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
} else if (ACTION_DELETE_FILE_SECURELY.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
@@ -398,13 +395,15 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEYRING.equals(action)) {
|
||||
try {
|
||||
List<ParcelableKeyRing> entries = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||
// get entries from cached file
|
||||
FileImportCache cache = new FileImportCache(this);
|
||||
List<ParcelableKeyRing> entries = cache.readCache();
|
||||
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, this);
|
||||
OperationResults.ImportResult result = pgpImportExport.importKeyRings(entries);
|
||||
ImportKeyResult result = pgpImportExport.importKeyRings(entries);
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putParcelable(RESULT, result);
|
||||
resultData.putParcelable(RESULT_IMPORT, result);
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
@@ -488,7 +487,7 @@ public class KeychainIntentService extends IntentService
|
||||
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
WrappedPublicKeyRing keyring = providerHelper.getWrappedPublicKeyRing(dataUri);
|
||||
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||
|
||||
boolean uploaded = pgpImportExport.uploadKeyRingToServer(server, keyring);
|
||||
@@ -537,7 +536,6 @@ public class KeychainIntentService extends IntentService
|
||||
Intent importIntent = new Intent(this, KeychainIntentService.class);
|
||||
importIntent.setAction(ACTION_IMPORT_KEYRING);
|
||||
Bundle importData = new Bundle();
|
||||
importData.putParcelableArrayList(IMPORT_KEY_LIST, keyRings);
|
||||
importIntent.putExtra(EXTRA_DATA, importData);
|
||||
importIntent.putExtra(EXTRA_MESSENGER, mMessenger);
|
||||
|
||||
@@ -564,9 +562,9 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
WrappedPublicKeyRing publicRing = providerHelper.getWrappedPublicKeyRing(pubKeyId);
|
||||
WrappedSecretKeyRing secretKeyRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
|
||||
WrappedSecretKey certificationKey = secretKeyRing.getSubKey();
|
||||
CanonicalizedPublicKeyRing publicRing = providerHelper.getCanonicalizedPublicKeyRing(pubKeyId);
|
||||
CanonicalizedSecretKeyRing secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(masterKeyId);
|
||||
CanonicalizedSecretKey certificationKey = secretKeyRing.getSecretKey();
|
||||
if(!certificationKey.unlock(signaturePassphrase)) {
|
||||
throw new PgpGeneralException("Error extracting key (bad passphrase?)");
|
||||
}
|
||||
@@ -644,6 +642,12 @@ public class KeychainIntentService extends IntentService
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, OperationResultParcel data) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(OperationResultParcel.EXTRA_RESULT, data);
|
||||
sendMessageToHandler(arg1, null, bundle);
|
||||
}
|
||||
|
||||
private void sendMessageToHandler(Integer arg1, Bundle data) {
|
||||
sendMessageToHandler(arg1, null, data);
|
||||
}
|
||||
|
||||
@@ -25,12 +25,11 @@ import android.os.Message;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
@@ -102,9 +101,9 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
// show error from service
|
||||
if (data.containsKey(DATA_ERROR)) {
|
||||
AppMsg.makeText(mActivity,
|
||||
Notify.showNotify(mActivity,
|
||||
mActivity.getString(R.string.error_message, data.getString(DATA_ERROR)),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.Style.ERROR);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/** Represent the result of an operation.
|
||||
*
|
||||
@@ -21,6 +34,9 @@ import java.util.ArrayList;
|
||||
*
|
||||
*/
|
||||
public class OperationResultParcel implements Parcelable {
|
||||
|
||||
public static final String EXTRA_RESULT = "operation_result";
|
||||
|
||||
/** Holds the overall result, the number specifying varying degrees of success. The first bit
|
||||
* is 0 on overall success, 1 on overall failure. All other bits may be used for more specific
|
||||
* conditions. */
|
||||
@@ -67,9 +83,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
mType = type;
|
||||
mParameters = parameters;
|
||||
mIndent = indent;
|
||||
}
|
||||
public LogEntryParcel(LogLevel level, LogType type, Object... parameters) {
|
||||
this(level, type, 0, parameters);
|
||||
Log.v(Constants.TAG, "log: " + this.toString());
|
||||
}
|
||||
|
||||
public LogEntryParcel(Parcel source) {
|
||||
@@ -102,6 +116,77 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogEntryParcel{" +
|
||||
"mLevel=" + mLevel +
|
||||
", mType=" + mType +
|
||||
", mParameters=" + Arrays.toString(mParameters) +
|
||||
", mIndent=" + mIndent +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
String str;
|
||||
int duration, color;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) {
|
||||
|
||||
if (getLog().containsWarnings()) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
} else {
|
||||
duration = SuperToast.Duration.LONG;
|
||||
color = Style.GREEN;
|
||||
}
|
||||
|
||||
str = "operation succeeded!";
|
||||
// str = activity.getString(R.string.import_error);
|
||||
|
||||
} else {
|
||||
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
|
||||
str = "operation failed";
|
||||
// str = activity.getString(R.string.import_error);
|
||||
|
||||
}
|
||||
|
||||
boolean button = getLog() != null && !getLog().isEmpty();
|
||||
SuperCardToast toast = new SuperCardToast(activity,
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(duration);
|
||||
toast.setIndeterminate(duration == 0);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
new SuperToast.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResultParcel.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return toast;
|
||||
|
||||
}
|
||||
|
||||
/** This is an enum of all possible log events.
|
||||
@@ -123,6 +208,8 @@ public class OperationResultParcel implements Parcelable {
|
||||
*/
|
||||
public static enum LogType {
|
||||
|
||||
INTERNAL_ERROR (R.string.internal_error),
|
||||
|
||||
// import public
|
||||
MSG_IP(R.string.msg_ip),
|
||||
MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch),
|
||||
@@ -193,6 +280,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_KC_REVOKE_BAD_LOCAL (R.string.msg_kc_revoke_bad_local),
|
||||
MSG_KC_REVOKE_BAD_TIME (R.string.msg_kc_revoke_bad_time),
|
||||
MSG_KC_REVOKE_BAD_TYPE (R.string.msg_kc_revoke_bad_type),
|
||||
MSG_KC_REVOKE_BAD_TYPE_UID (R.string.msg_kc_revoke_bad_type_uid),
|
||||
MSG_KC_REVOKE_BAD (R.string.msg_kc_revoke_bad),
|
||||
MSG_KC_REVOKE_DUP (R.string.msg_kc_revoke_dup),
|
||||
MSG_KC_SUB (R.string.msg_kc_sub),
|
||||
@@ -224,6 +312,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert),
|
||||
MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup),
|
||||
MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old),
|
||||
MSG_KC_UID_REMOVE (R.string.msg_kc_uid_remove),
|
||||
|
||||
|
||||
// keyring consolidation
|
||||
@@ -233,9 +322,17 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),
|
||||
MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
|
||||
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
|
||||
MSG_MG_UNCHANGED (R.string.msg_mg_unchanged),
|
||||
|
||||
// secret key create
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_mr),
|
||||
MSG_CR (R.string.msg_cr),
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master),
|
||||
MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id),
|
||||
MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify),
|
||||
MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512),
|
||||
MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo),
|
||||
MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp),
|
||||
MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal),
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (R.string.msg_mr),
|
||||
@@ -243,10 +340,13 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),
|
||||
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
|
||||
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity),
|
||||
MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary),
|
||||
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
|
||||
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
|
||||
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
|
||||
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),
|
||||
MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old),
|
||||
MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),
|
||||
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),
|
||||
MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),
|
||||
MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id),
|
||||
@@ -257,6 +357,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_UID_ADD (R.string.msg_mf_uid_add),
|
||||
MSG_MF_UID_PRIMARY (R.string.msg_mf_uid_primary),
|
||||
MSG_MF_UID_REVOKE (R.string.msg_mf_uid_revoke),
|
||||
MSG_MF_UID_ERROR_EMPTY (R.string.msg_mf_uid_error_empty),
|
||||
MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error),
|
||||
MSG_MF_UNLOCK (R.string.msg_mf_unlock),
|
||||
;
|
||||
@@ -288,7 +389,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mResult);
|
||||
dest.writeTypedList(mLog);
|
||||
dest.writeTypedList(mLog.toList());
|
||||
}
|
||||
|
||||
public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() {
|
||||
@@ -301,20 +402,21 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
public static class OperationLog extends ArrayList<LogEntryParcel> {
|
||||
public static class OperationLog implements Iterable<LogEntryParcel> {
|
||||
|
||||
private final List<LogEntryParcel> mParcels = new ArrayList<LogEntryParcel>();
|
||||
|
||||
/// Simple convenience method
|
||||
public void add(LogLevel level, LogType type, int indent, Object... parameters) {
|
||||
Log.d(Constants.TAG, type.toString());
|
||||
add(new OperationResultParcel.LogEntryParcel(level, type, indent, parameters));
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, parameters));
|
||||
}
|
||||
|
||||
public void add(LogLevel level, LogType type, int indent) {
|
||||
add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
}
|
||||
|
||||
public boolean containsWarnings() {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
|
||||
if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) {
|
||||
return true;
|
||||
}
|
||||
@@ -322,6 +424,22 @@ public class OperationResultParcel implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addAll(List<LogEntryParcel> parcels) {
|
||||
mParcels.addAll(parcels);
|
||||
}
|
||||
|
||||
public List<LogEntryParcel> toList() {
|
||||
return mParcels;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mParcels.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<LogEntryParcel> iterator() {
|
||||
return mParcels.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
|
||||
public abstract class OperationResults {
|
||||
|
||||
public static class ImportResult extends OperationResultParcel {
|
||||
public static class ImportKeyResult extends OperationResultParcel {
|
||||
|
||||
public final int mNewKeys, mUpdatedKeys, mBadKeys;
|
||||
|
||||
@@ -47,15 +48,15 @@ public abstract class OperationResults {
|
||||
return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING;
|
||||
}
|
||||
|
||||
public ImportResult(Parcel source) {
|
||||
public ImportKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mNewKeys = source.readInt();
|
||||
mUpdatedKeys = source.readInt();
|
||||
mBadKeys = source.readInt();
|
||||
}
|
||||
|
||||
public ImportResult(int result, OperationLog log,
|
||||
int newKeys, int updatedKeys, int badKeys) {
|
||||
public ImportKeyResult(int result, OperationLog log,
|
||||
int newKeys, int updatedKeys, int badKeys) {
|
||||
super(result, log);
|
||||
mNewKeys = newKeys;
|
||||
mUpdatedKeys = updatedKeys;
|
||||
@@ -70,17 +71,17 @@ public abstract class OperationResults {
|
||||
dest.writeInt(mBadKeys);
|
||||
}
|
||||
|
||||
public static Creator<ImportResult> CREATOR = new Creator<ImportResult>() {
|
||||
public ImportResult createFromParcel(final Parcel source) {
|
||||
return new ImportResult(source);
|
||||
public static Creator<ImportKeyResult> CREATOR = new Creator<ImportKeyResult>() {
|
||||
public ImportKeyResult createFromParcel(final Parcel source) {
|
||||
return new ImportKeyResult(source);
|
||||
}
|
||||
|
||||
public ImportResult[] newArray(final int size) {
|
||||
return new ImportResult[size];
|
||||
public ImportKeyResult[] newArray(final int size) {
|
||||
return new ImportKeyResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void displayNotify(final Activity activity) {
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
@@ -88,11 +89,11 @@ public abstract class OperationResults {
|
||||
int duration, color;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & ImportResult.RESULT_ERROR) == 0) {
|
||||
if ((resultType & OperationResultParcel.RESULT_ERROR) == 0) {
|
||||
String withWarnings;
|
||||
|
||||
// Any warnings?
|
||||
if ((resultType & ImportResult.RESULT_WITH_WARNINGS) > 0) {
|
||||
if ((resultType & ImportKeyResult.RESULT_WITH_WARNINGS) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
withWarnings = activity.getResources().getString(R.string.import_with_warnings);
|
||||
@@ -106,7 +107,7 @@ public abstract class OperationResults {
|
||||
if (this.isOkBoth()) {
|
||||
str = activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_added_and_updated_1, mNewKeys, mNewKeys);
|
||||
str += activity.getResources().getQuantityString(
|
||||
str += " "+ activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_added_and_updated_2, mUpdatedKeys, mUpdatedKeys, withWarnings);
|
||||
} else if (isOkUpdated()) {
|
||||
str = activity.getResources().getQuantityString(
|
||||
@@ -142,7 +143,7 @@ public abstract class OperationResults {
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.import_view_log));
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
@@ -151,18 +152,59 @@ public abstract class OperationResults {
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportResult.this);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
toast.show();
|
||||
|
||||
return toast;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class EditKeyResult extends OperationResultParcel {
|
||||
|
||||
private transient UncachedKeyRing mRing;
|
||||
public final Long mRingMasterKeyId;
|
||||
|
||||
public EditKeyResult(int result, OperationLog log,
|
||||
UncachedKeyRing ring) {
|
||||
super(result, log);
|
||||
mRing = ring;
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : null;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getRing() {
|
||||
return mRing;
|
||||
}
|
||||
|
||||
public EditKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mRingMasterKeyId = source.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mRingMasterKeyId);
|
||||
}
|
||||
|
||||
public static Creator<EditKeyResult> CREATOR = new Creator<EditKeyResult>() {
|
||||
public EditKeyResult createFromParcel(final Parcel source) {
|
||||
return new EditKeyResult(source);
|
||||
}
|
||||
|
||||
public EditKeyResult[] newArray(final int size) {
|
||||
return new EditKeyResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class SaveKeyringResult extends OperationResultParcel {
|
||||
|
||||
public SaveKeyringResult(int result, OperationLog log) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -25,6 +26,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
@@ -32,11 +34,15 @@ import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -54,6 +60,8 @@ public class PassphraseCacheService extends Service {
|
||||
+ "PASSPHRASE_CACHE_ADD";
|
||||
public static final String ACTION_PASSPHRASE_CACHE_GET = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_GET";
|
||||
public static final String ACTION_PASSPHRASE_CACHE_CLEAR = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_CLEAR";
|
||||
|
||||
public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX
|
||||
+ "PASSPHRASE_CACHE_BROADCAST";
|
||||
@@ -62,13 +70,16 @@ public class PassphraseCacheService extends Service {
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
public static final String EXTRA_USER_ID = "user_id";
|
||||
|
||||
private static final int REQUEST_ID = 0;
|
||||
private static final long DEFAULT_TTL = 15;
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
|
||||
private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
|
||||
private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<CachedPassphrase>();
|
||||
|
||||
Context mContext;
|
||||
|
||||
@@ -81,14 +92,17 @@ public class PassphraseCacheService extends Service {
|
||||
* @param keyId
|
||||
* @param passphrase
|
||||
*/
|
||||
public static void addCachedPassphrase(Context context, long keyId, String passphrase) {
|
||||
public static void addCachedPassphrase(Context context, long keyId, String passphrase,
|
||||
String primaryUserId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + keyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
||||
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl());
|
||||
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(EXTRA_USER_ID, primaryUserId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
@@ -159,42 +173,46 @@ public class PassphraseCacheService extends Service {
|
||||
// passphrase for symmetric encryption?
|
||||
if (keyId == Constants.key.symmetric) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption");
|
||||
String cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric);
|
||||
String cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric).getPassphrase();
|
||||
if (cachedPassphrase == null) {
|
||||
return null;
|
||||
}
|
||||
addCachedPassphrase(this, Constants.key.symmetric, cachedPassphrase);
|
||||
addCachedPassphrase(this, Constants.key.symmetric, cachedPassphrase, getString(R.string.passp_cache_notif_pwd));
|
||||
return cachedPassphrase;
|
||||
}
|
||||
|
||||
// try to get master key id which is used as an identifier for cached passphrases
|
||||
try {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId " + keyId);
|
||||
WrappedSecretKeyRing key = new ProviderHelper(this).getWrappedSecretKeyRing(
|
||||
CanonicalizedSecretKeyRing key = new ProviderHelper(this).getCanonicalizedSecretKeyRing(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId));
|
||||
// no passphrase needed? just add empty string and return it, then
|
||||
if (!key.hasPassphrase()) {
|
||||
Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
|
||||
|
||||
addCachedPassphrase(this, keyId, "");
|
||||
try {
|
||||
addCachedPassphrase(this, keyId, "", key.getPrimaryUserIdWithFallback());
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "PgpGeneralException occured");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// get cached passphrase
|
||||
String cachedPassphrase = mPassphraseCache.get(keyId);
|
||||
CachedPassphrase cachedPassphrase = mPassphraseCache.get(keyId);
|
||||
if (cachedPassphrase == null) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Passphrase not (yet) cached, returning null");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Passphrase not (yet) cached, returning null");
|
||||
// not really an error, just means the passphrase is not cached but not empty either
|
||||
return null;
|
||||
}
|
||||
|
||||
// set it again to reset the cache life cycle
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Cache passphrase again when getting it!");
|
||||
addCachedPassphrase(this, keyId, cachedPassphrase);
|
||||
return cachedPassphrase;
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Cache passphrase again when getting it!");
|
||||
addCachedPassphrase(this, keyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID());
|
||||
return cachedPassphrase.getPassphrase();
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Passphrase for unknown key was requested!");
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -211,7 +229,7 @@ public class PassphraseCacheService extends Service {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Received broadcast...");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Received broadcast...");
|
||||
|
||||
if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
@@ -236,10 +254,8 @@ public class PassphraseCacheService extends Service {
|
||||
private static PendingIntent buildIntent(Context context, long keyId) {
|
||||
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_ID, intent,
|
||||
return PendingIntent.getBroadcast(context, REQUEST_ID, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
return sender;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,14 +272,17 @@ public class PassphraseCacheService extends Service {
|
||||
if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
|
||||
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
|
||||
String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE);
|
||||
String primaryUserID = intent.getStringExtra(EXTRA_USER_ID);
|
||||
|
||||
Log.d(Constants.TAG,
|
||||
"PassphraseCacheService Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
|
||||
+ keyId + ", ttl: " + ttl);
|
||||
"PassphraseCacheService: Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: "
|
||||
+ keyId + ", ttl: " + ttl + ", usrId: " + primaryUserID
|
||||
);
|
||||
|
||||
// add keyId and passphrase to memory
|
||||
mPassphraseCache.put(keyId, passphrase);
|
||||
// add keyId, passphrase and primary user id to memory
|
||||
mPassphraseCache.put(keyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
@@ -271,6 +290,8 @@ public class PassphraseCacheService extends Service {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId));
|
||||
}
|
||||
|
||||
updateService();
|
||||
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
@@ -284,10 +305,21 @@ public class PassphraseCacheService extends Service {
|
||||
try {
|
||||
messenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Sending message failed", e);
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e);
|
||||
}
|
||||
} else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
// Stop all ttl alarms
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
}
|
||||
|
||||
mPassphraseCache.clear();
|
||||
|
||||
updateService();
|
||||
} else {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService Intent or Intent Action not supported!");
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,13 +338,76 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");
|
||||
|
||||
// stop whole service if no cached passphrases remaining
|
||||
if (mPassphraseCache.size() == 0) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheServic No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
updateService();
|
||||
}
|
||||
|
||||
private void updateService() {
|
||||
if (mPassphraseCache.size() > 0) {
|
||||
startForeground(NOTIFICATION_ID, getNotification());
|
||||
} else {
|
||||
// stop whole service if no cached passphrases remaining
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
|
||||
private Notification getNotification() {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys),
|
||||
mPassphraseCache.size()));
|
||||
|
||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
||||
|
||||
inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));
|
||||
|
||||
// Moves events into the big view
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID());
|
||||
}
|
||||
|
||||
// Moves the big view style object into the notification object.
|
||||
builder.setStyle(inboxStyle);
|
||||
|
||||
// Add purging action
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
builder.addAction(
|
||||
R.drawable.abc_ic_clear_normal,
|
||||
getString(R.string.passp_cache_notif_clear),
|
||||
PendingIntent.getService(
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Fallback, since expandable notifications weren't available back then
|
||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys,
|
||||
mPassphraseCache.size())))
|
||||
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
||||
|
||||
Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
|
||||
builder.setContentIntent(
|
||||
PendingIntent.getService(
|
||||
getApplicationContext(),
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
@@ -341,4 +436,29 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
private final IBinder mBinder = new PassphraseCacheBinder();
|
||||
|
||||
}
|
||||
public class CachedPassphrase {
|
||||
private String primaryUserID;
|
||||
private String passphrase;
|
||||
|
||||
public CachedPassphrase(String passphrase, String primaryUserID) {
|
||||
setPassphrase(passphrase);
|
||||
setPrimaryUserID(primaryUserID);
|
||||
}
|
||||
|
||||
public String getPrimaryUserID() {
|
||||
return primaryUserID;
|
||||
}
|
||||
|
||||
public String getPassphrase() {
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
public void setPrimaryUserID(String primaryUserID) {
|
||||
this.primaryUserID = primaryUserID;
|
||||
}
|
||||
|
||||
public void setPassphrase(String passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,23 +27,19 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
// the key fingerprint, for safety. MUST be null for a new key.
|
||||
public byte[] mFingerprint;
|
||||
|
||||
public String newPassphrase;
|
||||
public String mNewPassphrase;
|
||||
|
||||
public ArrayList<String> addUserIds;
|
||||
public ArrayList<SubkeyAdd> addSubKeys;
|
||||
public ArrayList<String> mAddUserIds;
|
||||
public ArrayList<SubkeyAdd> mAddSubKeys;
|
||||
|
||||
public ArrayList<SubkeyChange> changeSubKeys;
|
||||
public String changePrimaryUserId;
|
||||
public ArrayList<SubkeyChange> mChangeSubKeys;
|
||||
public String mChangePrimaryUserId;
|
||||
|
||||
public ArrayList<String> revokeUserIds;
|
||||
public ArrayList<Long> revokeSubKeys;
|
||||
public ArrayList<String> mRevokeUserIds;
|
||||
public ArrayList<Long> mRevokeSubKeys;
|
||||
|
||||
public SaveKeyringParcel() {
|
||||
addUserIds = new ArrayList<String>();
|
||||
addSubKeys = new ArrayList<SubkeyAdd>();
|
||||
changeSubKeys = new ArrayList<SubkeyChange>();
|
||||
revokeUserIds = new ArrayList<String>();
|
||||
revokeSubKeys = new ArrayList<Long>();
|
||||
reset();
|
||||
}
|
||||
|
||||
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
|
||||
@@ -52,6 +48,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mNewPassphrase = null;
|
||||
mAddUserIds = new ArrayList<String>();
|
||||
mAddSubKeys = new ArrayList<SubkeyAdd>();
|
||||
mChangePrimaryUserId = null;
|
||||
mChangeSubKeys = new ArrayList<SubkeyChange>();
|
||||
mRevokeUserIds = new ArrayList<String>();
|
||||
mRevokeSubKeys = new ArrayList<Long>();
|
||||
}
|
||||
|
||||
// performance gain for using Parcelable here would probably be negligible,
|
||||
// use Serializable instead.
|
||||
public static class SubkeyAdd implements Serializable {
|
||||
@@ -70,6 +76,7 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
public static class SubkeyChange implements Serializable {
|
||||
public long mKeyId;
|
||||
public Integer mFlags;
|
||||
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
||||
public Long mExpiry;
|
||||
public SubkeyChange(long keyId, Integer flags, Long expiry) {
|
||||
mKeyId = keyId;
|
||||
@@ -82,16 +89,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
mFingerprint = source.createByteArray();
|
||||
|
||||
newPassphrase = source.readString();
|
||||
mNewPassphrase = source.readString();
|
||||
|
||||
addUserIds = source.createStringArrayList();
|
||||
addSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
|
||||
mAddUserIds = source.createStringArrayList();
|
||||
mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
|
||||
|
||||
changeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
|
||||
changePrimaryUserId = source.readString();
|
||||
mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
|
||||
mChangePrimaryUserId = source.readString();
|
||||
|
||||
revokeUserIds = source.createStringArrayList();
|
||||
revokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
mRevokeUserIds = source.createStringArrayList();
|
||||
mRevokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,16 +109,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
}
|
||||
destination.writeByteArray(mFingerprint);
|
||||
|
||||
destination.writeString(newPassphrase);
|
||||
destination.writeString(mNewPassphrase);
|
||||
|
||||
destination.writeStringList(addUserIds);
|
||||
destination.writeSerializable(addSubKeys);
|
||||
destination.writeStringList(mAddUserIds);
|
||||
destination.writeSerializable(mAddSubKeys);
|
||||
|
||||
destination.writeSerializable(changeSubKeys);
|
||||
destination.writeString(changePrimaryUserId);
|
||||
destination.writeSerializable(mChangeSubKeys);
|
||||
destination.writeString(mChangePrimaryUserId);
|
||||
|
||||
destination.writeStringList(revokeUserIds);
|
||||
destination.writeSerializable(revokeSubKeys);
|
||||
destination.writeStringList(mRevokeUserIds);
|
||||
destination.writeSerializable(mRevokeSubKeys);
|
||||
}
|
||||
|
||||
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Helper for tests of the Keyring import in ProviderHelper.
|
||||
*/
|
||||
public class KeyringTestingHelper {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public KeyringTestingHelper(Context robolectricContext) {
|
||||
this.context = robolectricContext;
|
||||
}
|
||||
|
||||
public boolean addKeyring(Collection<String> blobFiles) throws Exception {
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(context);
|
||||
|
||||
byte[] data = TestDataUtil.readAllFully(blobFiles);
|
||||
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
|
||||
long masterKeyId = ring.getMasterKeyId();
|
||||
|
||||
// Should throw an exception; key is not yet saved
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
|
||||
|
||||
OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
|
||||
|
||||
boolean saveSuccess = saveKeyringResult.success();
|
||||
|
||||
// Now re-retrieve the saved key. Should not throw an exception.
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
|
||||
// A different ID should still fail
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
|
||||
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
|
||||
try {
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
throw new AssertionError("Was expecting the previous call to fail!");
|
||||
} catch (ProviderHelper.NotFoundException expectedException) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* For functional tests of PgpDecryptVerify
|
||||
*/
|
||||
public class PgpVerifyTestingHelper {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public PgpVerifyTestingHelper(Context robolectricContext) {
|
||||
this.context = robolectricContext;
|
||||
}
|
||||
|
||||
public int doTestFile(String testFileName) throws Exception {
|
||||
ProviderHelper providerHelper = new ProviderHelperStub(context);
|
||||
|
||||
PgpDecryptVerify.PassphraseCache passphraseCache = new PgpDecryptVerify.PassphraseCache() {
|
||||
public String getCachedPassphrase(long masterKeyId) {
|
||||
return "I am a passphrase";
|
||||
}
|
||||
};
|
||||
|
||||
byte[] sampleInputBytes = TestDataUtil.readFully(getClass().getResourceAsStream(testFileName));
|
||||
|
||||
InputStream sampleInput = new ByteArrayInputStream(sampleInputBytes);
|
||||
|
||||
InputData data = new InputData(sampleInput, sampleInputBytes.length);
|
||||
OutputStream outStream = new ByteArrayOutputStream();
|
||||
|
||||
PgpDecryptVerify verify = new PgpDecryptVerify.Builder(providerHelper, passphraseCache, data, outStream).build();
|
||||
PgpDecryptVerifyResult result = verify.execute();
|
||||
|
||||
return result.getSignatureResult().getStatus();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
/**
|
||||
* Created by art on 21/06/14.
|
||||
*/
|
||||
class ProviderHelperStub extends ProviderHelper {
|
||||
public ProviderHelperStub(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
|
||||
return new WrappedPublicKeyRing(data, false, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Misc support functions. Would just use Guava / Apache Commons but
|
||||
* avoiding extra dependencies.
|
||||
*/
|
||||
public class TestDataUtil {
|
||||
public static byte[] readFully(InputStream input) {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
appendToOutput(input, output);
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private static void appendToOutput(InputStream input, ByteArrayOutputStream output) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
try {
|
||||
while ((bytesRead = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readAllFully(Collection<String> inputResources) {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
for (String inputResource : inputResources) {
|
||||
appendToOutput(getResourceAsStream(inputResource), output);
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static InputStream getResourceAsStream(String resourceName) {
|
||||
return TestDataUtil.class.getResourceAsStream(resourceName);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Test support classes.
|
||||
* This is only in main code because of gradle-Android Studio-robolectric issues. Having
|
||||
* classes in main code means IDE autocomplete, class detection, etc., all works.
|
||||
* TODO Move into test package when possible
|
||||
*/
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -37,12 +38,11 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
@@ -52,10 +52,12 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -64,7 +66,8 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private View mSignButton;
|
||||
private View mCertifyButton;
|
||||
private ImageView mActionCertifyImage;
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
private Spinner mSelectKeyserverSpinner;
|
||||
|
||||
@@ -88,10 +91,19 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.sign_key_select_key_fragment);
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
||||
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
|
||||
mCertifyButton = findViewById(R.id.certify_key_certify_button);
|
||||
mActionCertifyImage = (ImageView) findViewById(R.id.certify_key_action_certify_image);
|
||||
mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
|
||||
|
||||
// make certify image gray, like action icons
|
||||
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
mSelectKeyFragment.setFilterCertify(true);
|
||||
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.getKeyServers()
|
||||
@@ -99,7 +111,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mSelectKeyserverSpinner.setAdapter(adapter);
|
||||
|
||||
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
|
||||
if (!mUploadKeyCheckbox.isChecked()) {
|
||||
mSelectKeyserverSpinner.setEnabled(false);
|
||||
} else {
|
||||
@@ -118,14 +129,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
});
|
||||
|
||||
mSignButton = findViewById(R.id.sign_key_sign_button);
|
||||
mSignButton.setOnClickListener(new OnClickListener() {
|
||||
mCertifyButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mPubKeyId != 0) {
|
||||
if (mMasterKeyId == 0) {
|
||||
mSelectKeyFragment.setError(getString(R.string.select_key_to_certify));
|
||||
Notify.showNotify(CertifyKeyActivity.this, getString(R.string.select_key_to_certify),
|
||||
Notify.Style.ERROR);
|
||||
} else {
|
||||
initiateSigning();
|
||||
}
|
||||
@@ -141,7 +153,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
Log.e(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
|
||||
|
||||
mUserIdsAdapter = new UserIdsAdapter(this, null, 0, true);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
@@ -230,7 +241,8 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
startSigning();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
return;
|
||||
} else {
|
||||
@@ -246,8 +258,8 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
// Bail out if there is not at least one user id selected
|
||||
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
|
||||
if (userIds.isEmpty()) {
|
||||
AppMsg.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(CertifyKeyActivity.this, "No identities selected!",
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -274,8 +286,8 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
AppMsg.makeText(CertifyKeyActivity.this, R.string.key_certify_success,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(CertifyKeyActivity.this, R.string.key_certify_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
@@ -327,8 +339,10 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
AppMsg.makeText(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OperationResultParcel.EXTRA_RESULT, message.getData());
|
||||
Notify.showNotify(CertifyKeyActivity.this, R.string.key_send_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class CreateKeyActivity extends ActionBarActivity {
|
||||
|
||||
public static final String EXTRA_NAME = "name";
|
||||
public static final String EXTRA_EMAIL = "email";
|
||||
|
||||
public static final int FRAG_ACTION_START = 0;
|
||||
public static final int FRAG_ACTION_TO_RIGHT = 1;
|
||||
public static final int FRAG_ACTION_TO_LEFT = 2;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.create_key_activity);
|
||||
|
||||
// pass extras into fragment
|
||||
CreateKeyInputFragment frag =
|
||||
CreateKeyInputFragment.newInstance(
|
||||
getIntent().getStringExtra(EXTRA_NAME),
|
||||
getIntent().getStringExtra(EXTRA_EMAIL)
|
||||
);
|
||||
loadFragment(null, frag, FRAG_ACTION_START);
|
||||
}
|
||||
|
||||
public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
switch (action) {
|
||||
case FRAG_ACTION_START:
|
||||
transaction.setCustomAnimations(0, 0);
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
case FRAG_ACTION_TO_LEFT:
|
||||
getSupportFragmentManager().popBackStackImmediate();
|
||||
break;
|
||||
case FRAG_ACTION_TO_RIGHT:
|
||||
transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left,
|
||||
R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right);
|
||||
transaction.addToBackStack(null);
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
|
||||
}
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
public class CreateKeyFinalFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
TextView mNameEdit;
|
||||
TextView mEmailEdit;
|
||||
CheckBox mUploadCheckbox;
|
||||
View mBackButton;
|
||||
View mCreateButton;
|
||||
|
||||
public static final String ARG_NAME = "name";
|
||||
public static final String ARG_EMAIL = "email";
|
||||
public static final String ARG_PASSPHRASE = "passphrase";
|
||||
|
||||
String mName;
|
||||
String mEmail;
|
||||
String mPassphrase;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CreateKeyFinalFragment newInstance(String name, String email, String passphrase) {
|
||||
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_NAME, name);
|
||||
args.putString(ARG_EMAIL, email);
|
||||
args.putString(ARG_PASSPHRASE, passphrase);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_key_final_fragment, container, false);
|
||||
|
||||
mNameEdit = (TextView) view.findViewById(R.id.name);
|
||||
mEmailEdit = (TextView) view.findViewById(R.id.email);
|
||||
mUploadCheckbox = (CheckBox) view.findViewById(R.id.create_key_upload);
|
||||
mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mCreateButton = view.findViewById(R.id.create_key_create_button);
|
||||
|
||||
// get args
|
||||
mName = getArguments().getString(ARG_NAME);
|
||||
mEmail = getArguments().getString(ARG_EMAIL);
|
||||
mPassphrase = getArguments().getString(ARG_PASSPHRASE);
|
||||
|
||||
// set values
|
||||
mNameEdit.setText(mName);
|
||||
mEmailEdit.setText(mEmail);
|
||||
|
||||
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKey();
|
||||
}
|
||||
});
|
||||
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mCreateKeyActivity.loadFragment(null, null, CreateKeyActivity.FRAG_ACTION_TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
getActivity(),
|
||||
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();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final OperationResults.EditKeyResult result =
|
||||
returnData.getParcelable(OperationResultParcel.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mUploadCheckbox.isChecked()) {
|
||||
if (result.getResult() == OperationResultParcel.RESULT_OK) {
|
||||
// result will be displayed after upload
|
||||
uploadKey(result);
|
||||
} else {
|
||||
// display result on error without finishing activity
|
||||
result.createNotify(getActivity());
|
||||
}
|
||||
} else {
|
||||
// TODO: return result
|
||||
result.createNotify(getActivity());
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null));
|
||||
String userId = mName + " <" + mEmail + ">";
|
||||
parcel.mAddUserIds.add(userId);
|
||||
parcel.mNewPassphrase = mPassphrase;
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
private void uploadKey(final OperationResults.EditKeyResult editKeyResult) {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
||||
|
||||
// set data uri as path to keyring
|
||||
Uri blobUri = KeychainContract.KeyRingData.buildPublicKeyRingUri(
|
||||
Long.toString(editKeyResult.mRingMasterKeyId)
|
||||
);
|
||||
intent.setData(blobUri);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
// upload to favorite keyserver
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getKeyServers()[0];
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// TODO: return results
|
||||
|
||||
editKeyResult.createNotify(getActivity());
|
||||
|
||||
Notify.showNotify(getActivity(), R.string.key_send_success,
|
||||
Notify.Style.INFO);
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class CreateKeyInputFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
AutoCompleteTextView mNameEdit;
|
||||
AutoCompleteTextView mEmailEdit;
|
||||
EditText mPassphraseEdit;
|
||||
EditText mPassphraseEditAgain;
|
||||
View mCreateButton;
|
||||
|
||||
public static final String ARG_NAME = "name";
|
||||
public static final String ARG_EMAIL = "email";
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CreateKeyInputFragment newInstance(String name, String email) {
|
||||
CreateKeyInputFragment frag = new CreateKeyInputFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_NAME, name);
|
||||
args.putString(ARG_EMAIL, email);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_key_input_fragment, container, false);
|
||||
|
||||
mNameEdit = (AutoCompleteTextView) view.findViewById(R.id.name);
|
||||
mEmailEdit = (AutoCompleteTextView) view.findViewById(R.id.email);
|
||||
mPassphraseEdit = (EditText) view.findViewById(R.id.passphrase);
|
||||
mPassphraseEditAgain = (EditText) view.findViewById(R.id.passphrase_again);
|
||||
mCreateButton = view.findViewById(R.id.create_key_button);
|
||||
|
||||
// initial values
|
||||
String name = getArguments().getString(ARG_NAME);
|
||||
String email = getArguments().getString(ARG_EMAIL);
|
||||
mNameEdit.setText(name);
|
||||
mEmailEdit.setText(email);
|
||||
|
||||
// focus non-empty edit fields
|
||||
if (name != null && email != null) {
|
||||
mPassphraseEdit.requestFocus();
|
||||
} else if (name != null) {
|
||||
mEmailEdit.requestFocus();
|
||||
}
|
||||
|
||||
mEmailEdit.setThreshold(1); // Start working from first character
|
||||
mEmailEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(getActivity())
|
||||
)
|
||||
);
|
||||
mEmailEdit.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String email = editable.toString();
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
|
||||
if (emailMatcher.matches()) {
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_ok, 0);
|
||||
} else {
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
mEmailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mNameEdit.setThreshold(1); // Start working from first character
|
||||
mNameEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(getActivity())
|
||||
)
|
||||
);
|
||||
|
||||
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKeyCheck();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
private void createKeyCheck() {
|
||||
if (isEditTextNotEmpty(getActivity(), mNameEdit)
|
||||
&& isEditTextNotEmpty(getActivity(), mEmailEdit)
|
||||
&& isEditTextNotEmpty(getActivity(), mPassphraseEdit)
|
||||
&& areEditTextsEqual(getActivity(), mPassphraseEdit, mPassphraseEditAgain)) {
|
||||
|
||||
CreateKeyFinalFragment frag =
|
||||
CreateKeyFinalFragment.newInstance(
|
||||
mNameEdit.getText().toString(),
|
||||
mEmailEdit.getText().toString(),
|
||||
mPassphraseEdit.getText().toString()
|
||||
);
|
||||
|
||||
hideKeyboard();
|
||||
mCreateKeyActivity.loadFragment(null, frag, CreateKeyActivity.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
//check if no view has focus:
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().toString().length() == 0) {
|
||||
editText.setError(context.getString(R.string.create_key_empty));
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static boolean areEditTextsEqual(Context context, EditText editText1, EditText editText2) {
|
||||
boolean output = true;
|
||||
if (!editText1.getText().toString().equals(editText2.getText().toString())) {
|
||||
editText2.setError(context.getString(R.string.create_key_passphrases_not_equal));
|
||||
editText2.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText2.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,9 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -107,7 +110,6 @@ public class DecryptFileFragment extends DecryptFragment {
|
||||
|
||||
private void decryptAction() {
|
||||
if (mInputUri == null) {
|
||||
//AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public abstract class DecryptFragment extends Fragment {
|
||||
mResultText.setText(R.string.decrypt_result_decrypted_and_signature_certified);
|
||||
}
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_green));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_green_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@@ -146,7 +146,7 @@ public abstract class DecryptFragment extends Fragment {
|
||||
mResultText.setText(R.string.decrypt_result_decrypted_and_signature_uncertified);
|
||||
}
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_orange));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_orange_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@@ -160,7 +160,7 @@ public abstract class DecryptFragment extends Fragment {
|
||||
mResultText.setText(R.string.decrypt_result_decrypted_unknown_pub_key);
|
||||
}
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_orange));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_orange_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
mLookupKey.setVisibility(View.VISIBLE);
|
||||
@@ -170,7 +170,7 @@ public abstract class DecryptFragment extends Fragment {
|
||||
case OpenPgpSignatureResult.SIGNATURE_ERROR: {
|
||||
mResultText.setText(R.string.decrypt_result_invalid_signature);
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_red));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_red_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@@ -180,7 +180,7 @@ public abstract class DecryptFragment extends Fragment {
|
||||
default: {
|
||||
mResultText.setText(R.string.error);
|
||||
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_red));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_red_light));
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
@@ -192,7 +192,7 @@ public abstract class DecryptFragment extends Fragment {
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
|
||||
// successful decryption-only
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_purple));
|
||||
mResultLayout.setBackgroundColor(getResources().getColor(R.color.android_purple_light));
|
||||
mResultText.setText(R.string.decrypt_result_decrypted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@@ -105,12 +106,10 @@ public class DecryptMessageFragment extends DecryptFragment {
|
||||
mCiphertext = matcher.group(1);
|
||||
decryptStart(null);
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
|
||||
}
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.edit_key_activity_new);
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
|
||||
@@ -1,744 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.ui;
|
||||
|
||||
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.v4.app.ActivityCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SectionView;
|
||||
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivityOld extends ActionBarActivity implements EditorListener {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
|
||||
public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
|
||||
|
||||
// possible extra keys
|
||||
public static final String EXTRA_USER_IDS = "user_ids";
|
||||
public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
|
||||
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
|
||||
|
||||
// EDIT
|
||||
private Uri mDataUri;
|
||||
|
||||
private SectionView mUserIdsView;
|
||||
private SectionView mKeysView;
|
||||
|
||||
private String mCurrentPassphrase = null;
|
||||
private String mNewPassphrase = null;
|
||||
private String mSavedNewPassphrase = null;
|
||||
private boolean mIsPassphraseSet;
|
||||
private boolean mNeedsSaving;
|
||||
private boolean mIsBrandNewKeyring = false;
|
||||
|
||||
private Button mChangePassphrase;
|
||||
|
||||
private CheckBox mNoPassphrase;
|
||||
|
||||
Vector<String> mUserIds;
|
||||
Vector<UncachedSecretKey> mKeys;
|
||||
Vector<Integer> mKeysUsages;
|
||||
boolean mMasterCanSign = true;
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
public boolean needsSaving() {
|
||||
mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
|
||||
mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
|
||||
mNeedsSaving |= hasPassphraseChanged();
|
||||
mNeedsSaving |= mIsBrandNewKeyring;
|
||||
return mNeedsSaving;
|
||||
}
|
||||
|
||||
|
||||
public void somethingChanged() {
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
}
|
||||
|
||||
public void onDeleted(Editor e, boolean wasNewItem) {
|
||||
somethingChanged();
|
||||
}
|
||||
|
||||
public void onEdited() {
|
||||
somethingChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
R.string.btn_save, R.drawable.ic_action_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Save
|
||||
saveClicked();
|
||||
}
|
||||
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Cancel
|
||||
cancelClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
mUserIds = new Vector<String>();
|
||||
mKeys = new Vector<UncachedSecretKey>();
|
||||
mKeysUsages = new Vector<Integer>();
|
||||
|
||||
// Catch Intents opened from other apps
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (ACTION_CREATE_KEY.equals(action)) {
|
||||
handleActionCreateKey(intent);
|
||||
} else if (ACTION_EDIT_KEY.equals(action)) {
|
||||
handleActionEditKey(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to create new key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionCreateKey(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
mCurrentPassphrase = "";
|
||||
mIsBrandNewKeyring = true;
|
||||
|
||||
if (extras != null) {
|
||||
// if userId is given, prefill the fields
|
||||
if (extras.containsKey(EXTRA_USER_IDS)) {
|
||||
Log.d(Constants.TAG, "UserIds are given!");
|
||||
mUserIds.add(extras.getString(EXTRA_USER_IDS));
|
||||
}
|
||||
|
||||
// if no passphrase is given
|
||||
if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
|
||||
boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
|
||||
if (noPassphrase) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// generate key
|
||||
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
|
||||
/*
|
||||
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
|
||||
if (generateDefaultKeys) {
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
|
||||
mCurrentPassphrase);
|
||||
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after generating is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
|
||||
ProgressDialog.STYLE_HORIZONTAL, true,
|
||||
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
// Stop key generation on cancel
|
||||
stopService(serviceIntent);
|
||||
EditKeyActivity.this.setResult(Activity.RESULT_CANCELED);
|
||||
EditKeyActivity.this.finish();
|
||||
}
|
||||
}) {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getDataAsStringList();
|
||||
|
||||
ArrayList<UncachedSecretKey> newKeys =
|
||||
PgpConversionHelper.BytesToPGPSecretKeyList(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
||||
|
||||
ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
|
||||
KeychainIntentService.RESULT_KEY_USAGES);
|
||||
|
||||
if (newKeys.size() == keyUsageFlags.size()) {
|
||||
for (int i = 0; i < newKeys.size(); ++i) {
|
||||
mKeys.add(newKeys.get(i));
|
||||
mKeysUsages.add(keyUsageFlags.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
buildLayout(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(serviceIntent);
|
||||
}
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
buildLayout(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to edit existing key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionEditKey(Intent intent) {
|
||||
mDataUri = intent.getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
||||
finish();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
try {
|
||||
Uri secretUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
WrappedSecretKeyRing keyRing = new ProviderHelper(this).getWrappedSecretKeyRing(secretUri);
|
||||
|
||||
mMasterCanSign = keyRing.getSubKey().canCertify();
|
||||
for (WrappedSecretKey key : keyRing.secretKeyIterator()) {
|
||||
// Turn into uncached instance
|
||||
mKeys.add(key.getUncached());
|
||||
mKeysUsages.add(key.getKeyUsage()); // get usage when view is created
|
||||
}
|
||||
|
||||
boolean isSet = false;
|
||||
for (String userId : keyRing.getSubKey().getUserIds()) {
|
||||
Log.d(Constants.TAG, "Added userId " + userId);
|
||||
if (!isSet) {
|
||||
isSet = true;
|
||||
String[] parts = KeyRing.splitUserId(userId);
|
||||
if (parts[0] != null) {
|
||||
setTitle(parts[0]);
|
||||
}
|
||||
}
|
||||
mUserIds.add(userId);
|
||||
}
|
||||
|
||||
buildLayout(false);
|
||||
|
||||
mCurrentPassphrase = "";
|
||||
mIsPassphraseSet = keyRing.hasPassphrase();
|
||||
if (!mIsPassphraseSet) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "Keyring not found: " + e.getMessage(), e);
|
||||
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog to set a new passphrase
|
||||
*/
|
||||
private void showSetPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
// set new returned passphrase!
|
||||
mNewPassphrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
|
||||
updatePassphraseButtonText();
|
||||
somethingChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
// set title based on isPassphraseSet()
|
||||
int title;
|
||||
if (isPassphraseSet()) {
|
||||
title = R.string.title_change_passphrase;
|
||||
} else {
|
||||
title = R.string.title_set_passphrase;
|
||||
}
|
||||
|
||||
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
|
||||
messenger, null, title);
|
||||
|
||||
setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
|
||||
* id and key.
|
||||
*
|
||||
* @param newKeys
|
||||
*/
|
||||
private void buildLayout(boolean newKeys) {
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
// find views
|
||||
mChangePassphrase = (Button) 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(SectionView.TYPE_USER_ID);
|
||||
mUserIdsView.setCanBeEdited(mMasterCanSign);
|
||||
mUserIdsView.setUserIds(mUserIds);
|
||||
mUserIdsView.setEditorListener(this);
|
||||
container.addView(mUserIdsView);
|
||||
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeysView.setType(SectionView.TYPE_KEY);
|
||||
mKeysView.setCanBeEdited(mMasterCanSign);
|
||||
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
|
||||
mKeysView.setEditorListener(this);
|
||||
container.addView(mKeysView);
|
||||
|
||||
updatePassphraseButtonText();
|
||||
|
||||
mChangePassphrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
showSetPassphraseDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// disable passphrase when no passphrase checkbox is checked!
|
||||
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
// remove passphrase
|
||||
mSavedNewPassphrase = mNewPassphrase;
|
||||
mNewPassphrase = "";
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
} else {
|
||||
mNewPassphrase = mSavedNewPassphrase;
|
||||
mChangePassphrase.setVisibility(View.VISIBLE);
|
||||
}
|
||||
somethingChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long getMasterKeyId() {
|
||||
if (mKeysView.getEditors().getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((KeyEditor) mKeysView.getEditors().getChildAt(0)).getValue().getKeyId();
|
||||
}
|
||||
|
||||
public boolean isPassphraseSet() {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return true;
|
||||
} else if ((mIsPassphraseSet)
|
||||
|| (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPassphraseChanged() {
|
||||
if (mNoPassphrase != null) {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return mIsPassphraseSet;
|
||||
} else {
|
||||
return (mNewPassphrase != null && !mNewPassphrase.equals(""));
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
final long masterKeyId = getMasterKeyId();
|
||||
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
|
||||
try {
|
||||
if (!isPassphraseSet()) {
|
||||
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
|
||||
}
|
||||
|
||||
String passphrase;
|
||||
if (mIsPassphraseSet) {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
|
||||
} else {
|
||||
passphrase = "";
|
||||
}
|
||||
if (passphrase == null) {
|
||||
PassphraseDialogFragment.show(this, masterKeyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
|
||||
EditKeyActivityOld.this, masterKeyId);
|
||||
checkEmptyIDsWanted();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mCurrentPassphrase = passphrase;
|
||||
checkEmptyIDsWanted();
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
} else {
|
||||
AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEmptyIDsWanted() {
|
||||
try {
|
||||
ArrayList<String> userIDs = getUserIds(mUserIdsView);
|
||||
List<Boolean> newIDs = mUserIdsView.getNewIDFlags();
|
||||
ArrayList<String> originalIDs = mUserIdsView.getOriginalIDs();
|
||||
int curID = 0;
|
||||
for (String userID : userIDs) {
|
||||
if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
|
||||
EditKeyActivityOld.this);
|
||||
|
||||
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(EditKeyActivityOld.this.getString(R.string.ask_empty_id_ok));
|
||||
|
||||
alert.setPositiveButton(EditKeyActivityOld.this.getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
finallySaveClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
alert.setNegativeButton(this.getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
);
|
||||
alert.setCancelable(false);
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
curID++;
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()), AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
finallySaveClicked();
|
||||
}
|
||||
|
||||
private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
|
||||
final boolean[] primitives = new boolean[booleanList.size()];
|
||||
int index = 0;
|
||||
for (Boolean object : booleanList) {
|
||||
primitives[index++] = object;
|
||||
}
|
||||
return primitives;
|
||||
}
|
||||
|
||||
private void finallySaveClicked() {
|
||||
/*
|
||||
try {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
OldSaveKeyringParcel saveParams = new OldSaveKeyringParcel();
|
||||
saveParams.userIds = getUserIds(mUserIdsView);
|
||||
saveParams.originalIDs = mUserIdsView.getOriginalIDs();
|
||||
saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
|
||||
saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags());
|
||||
saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
|
||||
saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
|
||||
saveParams.deletedKeys = mKeysView.getDeletedKeys();
|
||||
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
|
||||
saveParams.keysUsages = getKeysUsages(mKeysView);
|
||||
saveParams.newPassphrase = mNewPassphrase;
|
||||
saveParams.oldPassphrase = mCurrentPassphrase;
|
||||
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
|
||||
saveParams.keys = getKeys(mKeysView);
|
||||
saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
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 KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Intent data = new Intent();
|
||||
|
||||
// return uri pointing to new created key
|
||||
Uri uri = KeyRings.buildGenericKeyRingUri(getMasterKeyId());
|
||||
data.setData(uri);
|
||||
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
if (needsSaving()) { //ask if we want to save
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
|
||||
EditKeyActivityOld.this);
|
||||
|
||||
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(EditKeyActivityOld.this.getString(R.string.ask_save_changed_key));
|
||||
|
||||
alert.setPositiveButton(EditKeyActivityOld.this.getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
saveClicked();
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(this.getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
alert.show();
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user ids from the SectionView
|
||||
*
|
||||
* @param userIdsView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<String> getUserIds(SectionView userIdsView) throws PgpGeneralException {
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
|
||||
ViewGroup userIdEditors = userIdsView.getEditors();
|
||||
|
||||
boolean gotMainUserId = false;
|
||||
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
||||
String userId;
|
||||
userId = editor.getValue();
|
||||
|
||||
if (editor.isMainUserId()) {
|
||||
userIds.add(0, userId);
|
||||
gotMainUserId = true;
|
||||
} else {
|
||||
userIds.add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (userIds.size() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_a_user_id));
|
||||
}
|
||||
|
||||
if (!gotMainUserId) {
|
||||
throw new PgpGeneralException(getString(R.string.error_main_user_id_must_not_be_empty));
|
||||
}
|
||||
|
||||
return userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<UncachedSecretKey> getKeys(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<UncachedSecretKey> keys = new ArrayList<UncachedSecretKey>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keys.add(editor.getValue());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns usage selections of keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getKeysUsages(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<Integer> keysUsages = new ArrayList<Integer>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keysUsages.add(editor.getUsage());
|
||||
}
|
||||
|
||||
return keysUsages;
|
||||
}
|
||||
|
||||
private ArrayList<Calendar> getKeysExpiryDates(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<Calendar> keysExpiryDates = new ArrayList<Calendar>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keysExpiryDates.add(editor.getExpiryDate());
|
||||
}
|
||||
|
||||
return keysExpiryDates;
|
||||
}
|
||||
|
||||
private void updatePassphraseButtonText() {
|
||||
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
|
||||
: getString(R.string.btn_set_passphrase));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,24 +42,28 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
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.OperationResults;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ChangeExpiryDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
public class EditKeyFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@@ -164,8 +168,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
try {
|
||||
Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
WrappedSecretKeyRing keyRing =
|
||||
new ProviderHelper(getActivity()).getWrappedSecretKeyRing(secretUri);
|
||||
CanonicalizedSecretKeyRing keyRing =
|
||||
new ProviderHelper(getActivity()).getCanonicalizedSecretKeyRing(secretUri);
|
||||
|
||||
mSaveKeyringParcel = new SaveKeyringParcel(keyRing.getMasterKeyId(),
|
||||
keyRing.getUncachedKeyRing().getFingerprint());
|
||||
@@ -214,10 +218,18 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mUserIdsAddedData);
|
||||
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
|
||||
|
||||
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0);
|
||||
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0, mSaveKeyringParcel);
|
||||
mSubkeysList.setAdapter(mSubkeysAdapter);
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.addSubKeys);
|
||||
mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
long keyId = mSubkeysAdapter.getKeyId(position);
|
||||
editSubkey(keyId);
|
||||
}
|
||||
});
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys);
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
@@ -287,7 +299,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
Bundle data = message.getData();
|
||||
|
||||
// cache new returned passphrase!
|
||||
mSaveKeyringParcel.newPassphrase = data
|
||||
mSaveKeyringParcel.mNewPassphrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
}
|
||||
}
|
||||
@@ -309,19 +321,19 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
switch (message.what) {
|
||||
case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.changePrimaryUserId = null;
|
||||
if (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = null;
|
||||
} else {
|
||||
mSaveKeyringParcel.changePrimaryUserId = userId;
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = userId;
|
||||
}
|
||||
break;
|
||||
case EditUserIdDialogFragment.MESSAGE_REVOKE:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.revokeUserIds.contains(userId)) {
|
||||
mSaveKeyringParcel.revokeUserIds.remove(userId);
|
||||
if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) {
|
||||
mSaveKeyringParcel.mRevokeUserIds.remove(userId);
|
||||
} else {
|
||||
mSaveKeyringParcel.revokeUserIds.add(userId);
|
||||
mSaveKeyringParcel.mRevokeUserIds.add(userId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -342,6 +354,72 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
});
|
||||
}
|
||||
|
||||
private void editSubkey(final long keyId) {
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY:
|
||||
editSubkeyExpiry(keyId);
|
||||
break;
|
||||
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) {
|
||||
mSaveKeyringParcel.mRevokeSubKeys.remove(keyId);
|
||||
} else {
|
||||
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
final Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||
public void run() {
|
||||
EditSubkeyDialogFragment dialogFragment =
|
||||
EditSubkeyDialogFragment.newInstance(messenger);
|
||||
|
||||
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void editSubkeyExpiry(final long keyId) {
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case ChangeExpiryDialogFragment.MESSAGE_NEW_EXPIRY_DATE:
|
||||
// toggle
|
||||
// if (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
// && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
|
||||
// mSaveKeyringParcel.changePrimaryUserId = null;
|
||||
// } else {
|
||||
// mSaveKeyringParcel.changePrimaryUserId = userId;
|
||||
// }
|
||||
break;
|
||||
}
|
||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
final Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||
public void run() {
|
||||
ChangeExpiryDialogFragment dialogFragment =
|
||||
ChangeExpiryDialogFragment.newInstance(messenger, new Date(), new Date());
|
||||
|
||||
dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addUserId() {
|
||||
mUserIdsAddedAdapter.add(new UserIdsAddedAdapter.UserIdModel());
|
||||
}
|
||||
@@ -373,10 +451,11 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
private void save(String passphrase) {
|
||||
Log.d(Constants.TAG, "add userids to parcel: " + mUserIdsAddedAdapter.getDataAsStringList());
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.newPassphrase: " + mSaveKeyringParcel.newPassphrase);
|
||||
mSaveKeyringParcel.mAddUserIds = mUserIdsAddedAdapter.getDataAsStringList();
|
||||
|
||||
mSaveKeyringParcel.addUserIds = mUserIdsAddedAdapter.getDataAsStringList();
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds);
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
@@ -388,26 +467,30 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
getActivity().finish();
|
||||
|
||||
// TODO below
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final OperationResults.SaveKeyringResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT);
|
||||
final OperationResults.EditKeyResult result =
|
||||
returnData.getParcelable(EditKeyResult.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if good -> finish, return result to showkey and display there!
|
||||
// if bad -> display here!
|
||||
if (!result.success()) {
|
||||
result.createNotify(getActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// result.displayNotify(ImportKeysActivity.this);
|
||||
// if good -> finish, return result to showkey and display there!
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EditKeyResult.EXTRA_RESULT, result);
|
||||
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
|
||||
getActivity().finish();
|
||||
|
||||
// getActivity().finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -432,4 +515,4 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class FirstTimeActivity extends ActionBarActivity {
|
||||
|
||||
View mCreateKey;
|
||||
View mImportKey;
|
||||
View mSkipSetup;
|
||||
|
||||
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.first_time_activity);
|
||||
|
||||
mCreateKey = findViewById(R.id.first_time_create_key);
|
||||
mImportKey = findViewById(R.id.first_time_import_key);
|
||||
mSkipSetup = findViewById(R.id.first_time_cancel);
|
||||
|
||||
mSkipSetup.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finishSetup();
|
||||
}
|
||||
});
|
||||
|
||||
mImportKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
mCreateKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FirstTimeActivity.this, CreateKeyActivity.class);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_CODE_CREATE_OR_IMPORT_KEY) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
finishSetup();
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "No valid request code!");
|
||||
}
|
||||
}
|
||||
|
||||
private void finishSetup() {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -40,17 +40,19 @@ 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.keyimport.FileImportCache;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -354,9 +356,8 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
ImportKeysServerFragment f = (ImportKeysServerFragment)
|
||||
getActiveFragment(mViewPager, TAB_KEYSERVER);
|
||||
|
||||
// TODO: Currently it simply uses keyserver nr 0
|
||||
String keyserver = Preferences.getPreferences(ImportKeysActivity.this)
|
||||
.getKeyServers()[0];
|
||||
// ask favorite keyserver
|
||||
String keyserver = Preferences.getPreferences(ImportKeysActivity.this).getKeyServers()[0];
|
||||
|
||||
// set fields of ImportKeysServerFragment
|
||||
f.setQueryAndKeyserver(query, keyserver);
|
||||
@@ -427,15 +428,15 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ImportResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT);
|
||||
final ImportKeyResult result =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_IMPORT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_RESULT, result);
|
||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||
ImportKeysActivity.this.setResult(RESULT_OK, intent);
|
||||
ImportKeysActivity.this.finish();
|
||||
return;
|
||||
@@ -451,7 +452,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
result.displayNotify(ImportKeysActivity.this);
|
||||
result.createNotify(ImportKeysActivity.this).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -470,19 +471,29 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
|
||||
// get DATA from selected key entries
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
// instead of given the entries by Intent extra, cache them into a file
|
||||
// to prevent Java Binder problems on heavy imports
|
||||
// read FileImportCache for more info.
|
||||
try {
|
||||
FileImportCache cache = new FileImportCache(this);
|
||||
cache.writeCache(selectedEntries);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Problem writing cache file", e);
|
||||
Notify.showNotify(this, "Problem writing cache file!", Notify.Style.ERROR);
|
||||
}
|
||||
} else if (ls instanceof ImportKeysListFragment.KeyserverLoaderState) {
|
||||
ImportKeysListFragment.KeyserverLoaderState sls = (ImportKeysListFragment.KeyserverLoaderState) ls;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
|
||||
@@ -288,13 +287,13 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
if (error == null) {
|
||||
// No error
|
||||
mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings();
|
||||
} else if (error instanceof ImportKeysListLoader.FileHasNoContent) {
|
||||
Notify.showNotify(getActivity(), R.string.error_import_file_no_content, Notify.Style.ERROR);
|
||||
} else if (error instanceof ImportKeysListLoader.NonPgpPart) {
|
||||
} else if (error instanceof ImportKeysListLoader.NoValidKeysException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_import_no_valid_keys, Notify.Style.ERROR);
|
||||
} else if (error instanceof ImportKeysListLoader.NonPgpPartException) {
|
||||
Notify.showNotify(getActivity(),
|
||||
((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources().
|
||||
((ImportKeysListLoader.NonPgpPartException) error).getCount() + " " + getResources().
|
||||
getQuantityString(R.plurals.error_import_non_pgp_part,
|
||||
((ImportKeysListLoader.NonPgpPart) error).getCount()),
|
||||
((ImportKeysListLoader.NonPgpPartException) error).getCount()),
|
||||
Notify.Style.OK
|
||||
);
|
||||
} else {
|
||||
@@ -308,9 +307,11 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
if (error == null) {
|
||||
// No error
|
||||
} else if (error instanceof Keyserver.QueryTooShortException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_keyserver_insufficient_query, Notify.Style.ERROR);
|
||||
Notify.showNotify(getActivity(), R.string.error_query_too_short, Notify.Style.ERROR);
|
||||
} else if (error instanceof Keyserver.TooManyResponsesException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_keyserver_too_many_responses, Notify.Style.ERROR);
|
||||
Notify.showNotify(getActivity(), R.string.error_too_many_responses, Notify.Style.ERROR);
|
||||
} else if (error instanceof Keyserver.QueryTooShortOrTooManyResponsesException) {
|
||||
Notify.showNotify(getActivity(), R.string.error_too_short_or_too_many_responses, Notify.Style.ERROR);
|
||||
} else if (error instanceof Keyserver.QueryFailedException) {
|
||||
Log.d(Constants.TAG,
|
||||
"Unrecoverable keyserver query error: " + error.getLocalizedMessage());
|
||||
|
||||
@@ -17,33 +17,21 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class KeyListActivity extends DrawerActivity {
|
||||
|
||||
@@ -53,6 +41,14 @@ public class KeyListActivity extends DrawerActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// if this is the first time show first time activity
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.isFirstTime()) {
|
||||
startActivity(new Intent(this, FirstTimeActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
setContentView(R.layout.key_list_activity);
|
||||
@@ -66,9 +62,10 @@ public class KeyListActivity extends DrawerActivity {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.key_list, menu);
|
||||
|
||||
if(Constants.DEBUG) {
|
||||
if (Constants.DEBUG) {
|
||||
menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
|
||||
menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -85,10 +82,6 @@ public class KeyListActivity extends DrawerActivity {
|
||||
createKey();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_create_expert:
|
||||
createKeyExpert();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_export:
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
return true;
|
||||
@@ -96,24 +89,32 @@ public class KeyListActivity extends DrawerActivity {
|
||||
case R.id.menu_key_list_debug_read:
|
||||
try {
|
||||
KeychainDatabase.debugRead(this);
|
||||
AppMsg.makeText(this, "Restored from backup", AppMsg.STYLE_CONFIRM).show();
|
||||
Notify.showNotify(this, "Restored Notify.Style backup", Notify.Style.INFO);
|
||||
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_write:
|
||||
try {
|
||||
KeychainDatabase.debugWrite(this);
|
||||
AppMsg.makeText(this, "Backup successful", AppMsg.STYLE_CONFIRM).show();
|
||||
Notify.showNotify(this, "Backup Notify.Style", Notify.Style.INFO);
|
||||
} catch(IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(this, "IO Notify.Style: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_first_time:
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(true);
|
||||
Intent intent = new Intent(this, FirstTimeActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -125,50 +126,8 @@ public class KeyListActivity extends DrawerActivity {
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, WizardActivity.class);
|
||||
// intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
// intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
// intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
|
||||
startActivityForResult(intent, 0);
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void createKeyExpert() {
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// 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);
|
||||
Bundle data = message.getData();
|
||||
// OtherHelper.logDebugBundle(data, "message reply");
|
||||
}
|
||||
};
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.addUserIds.add("swagerinho");
|
||||
parcel.newPassphrase = "swag";
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -46,14 +47,11 @@ import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
@@ -62,6 +60,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Highlighter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -86,6 +85,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
private Button mButtonEmptyCreate;
|
||||
private Button mButtonEmptyImport;
|
||||
|
||||
public static final int REQUEST_CODE_CREATE_OR_IMPORT_KEY = 0x00007012;
|
||||
|
||||
/**
|
||||
* Load custom layout with StickyListView from library
|
||||
@@ -104,11 +104,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), EditKeyActivityOld.class);
|
||||
intent.setAction(EditKeyActivityOld.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
intent.putExtra(EditKeyActivityOld.EXTRA_USER_IDS, ""); // show user id view
|
||||
startActivityForResult(intent, 0);
|
||||
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
mButtonEmptyImport = (Button) view.findViewById(R.id.key_list_empty_button_import);
|
||||
@@ -117,8 +114,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
|
||||
startActivityForResult(intent, 0);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -339,8 +336,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
|
||||
// Can only work on singular secret keys
|
||||
if(hasSecret && masterKeyIds.length > 1) {
|
||||
AppMsg.makeText(getActivity(), R.string.secret_cannot_multiple,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.secret_cannot_multiple,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -437,9 +434,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
private class ItemViewHolder {
|
||||
TextView mMainUserId;
|
||||
TextView mMainUserIdRest;
|
||||
FrameLayout mStatusLayout;
|
||||
TextView mRevoked;
|
||||
ImageView mVerified;
|
||||
ImageView mStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -448,9 +443,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
ItemViewHolder holder = new ItemViewHolder();
|
||||
holder.mMainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
holder.mStatusLayout = (FrameLayout) view.findViewById(R.id.status_layout);
|
||||
holder.mRevoked = (TextView) view.findViewById(R.id.revoked);
|
||||
holder.mVerified = (ImageView) view.findViewById(R.id.verified);
|
||||
holder.mStatus = (ImageView) view.findViewById(R.id.status_image);
|
||||
view.setTag(holder);
|
||||
return view;
|
||||
}
|
||||
@@ -486,25 +479,36 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
if (cursor.getInt(KeyListFragment.INDEX_HAS_ANY_SECRET) != 0) {
|
||||
// this is a secret key
|
||||
h.mStatusLayout.setVisibility(View.VISIBLE);
|
||||
h.mRevoked.setVisibility(View.GONE);
|
||||
h.mVerified.setVisibility(View.GONE);
|
||||
h.mStatus.setVisibility(View.GONE);
|
||||
} else {
|
||||
// this is a public key - show if it's revoked
|
||||
// this is a public key - show if it's revoked, expired, or verified
|
||||
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
boolean isExpired = !cursor.isNull(INDEX_EXPIRY)
|
||||
&& new Date(cursor.getLong(INDEX_EXPIRY)*1000).before(new Date());
|
||||
if(isRevoked || isExpired) {
|
||||
h.mStatusLayout.setVisibility(View.VISIBLE);
|
||||
h.mRevoked.setVisibility(View.VISIBLE);
|
||||
h.mVerified.setVisibility(View.GONE);
|
||||
h.mRevoked.setText(isRevoked ? R.string.revoked : R.string.expired);
|
||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
// Note: order is important!
|
||||
if (isRevoked) {
|
||||
h.mStatus.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_revoked_cutout));
|
||||
h.mStatus.setColorFilter(getResources().getColor(R.color.android_red_light),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (isExpired) {
|
||||
h.mStatus.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_expired_cutout));
|
||||
h.mStatus.setColorFilter(getResources().getColor(R.color.android_orange_light),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (isVerified) {
|
||||
h.mStatus.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_verified_cutout));
|
||||
h.mStatus.setColorFilter(getResources().getColor(R.color.android_green_light),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
h.mStatusLayout.setVisibility(isVerified ? View.VISIBLE : View.GONE);
|
||||
h.mRevoked.setVisibility(View.GONE);
|
||||
h.mVerified.setVisibility(isVerified ? View.VISIBLE : View.GONE);
|
||||
h.mStatus.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class LogDisplayFragment extends ListFragment implements OnTouchListener {
|
||||
|
||||
@@ -135,7 +136,7 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener
|
||||
private LayoutInflater mInflater;
|
||||
private int dipFactor;
|
||||
|
||||
public LogAdapter(Context context, ArrayList<LogEntryParcel> log, LogLevel level) {
|
||||
public LogAdapter(Context context, OperationResultParcel.OperationLog log, LogLevel level) {
|
||||
super(context, R.layout.log_display_item);
|
||||
mInflater = LayoutInflater.from(getContext());
|
||||
dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||
@@ -177,9 +178,11 @@ public class LogDisplayFragment extends ListFragment implements OnTouchListener
|
||||
if (entry.mParameters != null && entry.mParameters.length > 0
|
||||
&& entry.mParameters[0] instanceof Integer) {
|
||||
ih.mText.setText(getResources().getQuantityString(entry.mType.getMsgId(),
|
||||
(Integer) entry.mParameters[0], entry.mParameters));
|
||||
(Integer) entry.mParameters[0],
|
||||
entry.mParameters));
|
||||
} else {
|
||||
ih.mText.setText(getResources().getString(entry.mType.getMsgId(), entry.mParameters));
|
||||
ih.mText.setText(getResources().getString(entry.mType.getMsgId(),
|
||||
entry.mParameters));
|
||||
}
|
||||
ih.mText.setTextColor(entry.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK);
|
||||
convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0);
|
||||
|
||||
@@ -121,6 +121,9 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
initializeForceV3Signatures(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||
|
||||
initializeConcealPgpApplication(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION));
|
||||
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Load the legacy preferences headers
|
||||
addPreferencesFromResource(R.xml.preference_headers_legacy);
|
||||
@@ -264,6 +267,9 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
|
||||
initializeForceV3Signatures(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||
|
||||
initializeConcealPgpApplication(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.CONCEAL_PGP_APPLICATION));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,4 +402,15 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeConcealPgpApplication(final CheckBoxPreference mConcealPgpApplication) {
|
||||
mConcealPgpApplication.setChecked(sPreferences.getConcealPgpApplication());
|
||||
mConcealPgpApplication.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mConcealPgpApplication.setChecked((Boolean) newValue);
|
||||
sPreferences.setConcealPgpApplication((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
public class QrCodeActivity extends ActionBarActivity {
|
||||
|
||||
private ImageView mFingerprintQrCode;
|
||||
|
||||
private static final int QR_CODE_SIZE = 1000;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// "Done"
|
||||
finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.qr_code_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mFingerprintQrCode = (ImageView) findViewById(R.id.qr_code_image);
|
||||
|
||||
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
try {
|
||||
byte[] blob = (byte[]) providerHelper.getGenericData(
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeychainContract.KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if (blob == null) {
|
||||
Log.e(Constants.TAG, "key not found!");
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
|
||||
finish();
|
||||
}
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
mFingerprintQrCode.setImageBitmap(QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE));
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// custom activity transition to get zoom in effect
|
||||
this.overridePendingTransition(R.anim.qr_code_zoom_enter, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// custom activity transition to get zoom out effect
|
||||
this.overridePendingTransition(0, R.anim.qr_code_zoom_exit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
@@ -143,16 +143,16 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
try {
|
||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||
|
||||
WrappedPublicKeyRing signeeRing =
|
||||
providerHelper.getWrappedPublicKeyRing(data.getLong(INDEX_MASTER_KEY_ID));
|
||||
WrappedPublicKeyRing signerRing =
|
||||
providerHelper.getWrappedPublicKeyRing(sig.getKeyId());
|
||||
CanonicalizedPublicKeyRing signeeRing =
|
||||
providerHelper.getCanonicalizedPublicKeyRing(data.getLong(INDEX_MASTER_KEY_ID));
|
||||
CanonicalizedPublicKeyRing signerRing =
|
||||
providerHelper.getCanonicalizedPublicKeyRing(sig.getKeyId());
|
||||
|
||||
try {
|
||||
sig.init(signerRing.getSubkey());
|
||||
if (sig.verifySignature(signeeRing.getSubkey(), signeeUid)) {
|
||||
sig.init(signerRing.getPublicKey());
|
||||
if (sig.verifySignature(signeeRing.getPublicKey(), signeeUid)) {
|
||||
mStatus.setText(R.string.cert_verify_ok);
|
||||
mStatus.setTextColor(getResources().getColor(R.color.result_green));
|
||||
mStatus.setTextColor(getResources().getColor(R.color.android_green_light));
|
||||
} else {
|
||||
mStatus.setText(R.string.cert_verify_failed);
|
||||
mStatus.setTextColor(getResources().getColor(R.color.alert));
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
@@ -43,8 +43,9 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -54,11 +55,12 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -81,9 +83,11 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
private ViewPager mViewPager;
|
||||
private SlidingTabLayout mSlidingTabLayout;
|
||||
private PagerTabStripAdapter mTabsAdapter;
|
||||
|
||||
private LinearLayout mStatusLayout;
|
||||
private TextView mStatusText;
|
||||
private ImageView mStatusImage;
|
||||
private View mStatusDivider;
|
||||
private View mStatusRevoked;
|
||||
private View mStatusExpired;
|
||||
|
||||
public static final int REQUEST_CODE_LOOKUP_KEY = 0x00007006;
|
||||
|
||||
@@ -115,9 +119,10 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
setContentView(R.layout.view_key_activity);
|
||||
|
||||
mStatusDivider = findViewById(R.id.status_divider);
|
||||
mStatusRevoked = findViewById(R.id.view_key_revoked);
|
||||
mStatusExpired = findViewById(R.id.view_key_expired);
|
||||
mStatusLayout = (LinearLayout) findViewById(R.id.view_key_status_layout);
|
||||
mStatusText = (TextView) findViewById(R.id.view_key_status_text);
|
||||
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
|
||||
mStatusDivider = findViewById(R.id.view_key_status_divider);
|
||||
|
||||
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
|
||||
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout);
|
||||
@@ -295,7 +300,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Notify.Style.ERROR);
|
||||
Log.e(Constants.TAG, "Key not found", e);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -351,22 +356,11 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_LOOKUP_KEY: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
ImportResult result = data.getParcelableExtra(ImportKeysActivity.EXTRA_RESULT);
|
||||
if (result != null) {
|
||||
result.displayNotify(this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
if (data != null && data.hasExtra(OperationResultParcel.EXTRA_RESULT)) {
|
||||
OperationResultParcel result = data.getParcelableExtra(OperationResultParcel.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,8 +448,8 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case NFC_SENT:
|
||||
AppMsg.makeText(ViewKeyActivity.this, R.string.nfc_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(
|
||||
ViewKeyActivity.this, R.string.nfc_successful, Notify.Style.INFO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -514,22 +508,32 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
|
||||
getSupportActionBar().setSubtitle(keyIdStr);
|
||||
|
||||
// If this key is revoked, it cannot be used for anything!
|
||||
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusRevoked.setVisibility(View.VISIBLE);
|
||||
mStatusExpired.setVisibility(View.GONE);
|
||||
} else {
|
||||
mStatusRevoked.setVisibility(View.GONE);
|
||||
boolean isRevoked = data.getInt(INDEX_UNIFIED_IS_REVOKED) > 0;
|
||||
boolean isExpired = !data.isNull(INDEX_UNIFIED_EXPIRY)
|
||||
&& new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000).before(new Date());
|
||||
|
||||
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusExpired.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mStatusDivider.setVisibility(View.GONE);
|
||||
mStatusExpired.setVisibility(View.GONE);
|
||||
}
|
||||
// Note: order is important
|
||||
if (isRevoked) {
|
||||
mStatusText.setText(R.string.view_key_revoked);
|
||||
mStatusText.setTextColor(getResources().getColor(R.color.android_red_light));
|
||||
mStatusImage.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_revoked_cutout));
|
||||
mStatusImage.setColorFilter(getResources().getColor(R.color.android_red_light),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusLayout.setVisibility(View.VISIBLE);
|
||||
} else if (isExpired) {
|
||||
mStatusText.setText(R.string.view_key_expired);
|
||||
mStatusText.setTextColor(getResources().getColor(R.color.android_orange_light));
|
||||
mStatusImage.setImageDrawable(
|
||||
getResources().getDrawable(R.drawable.status_signature_expired_cutout));
|
||||
mStatusImage.setColorFilter(getResources().getColor(R.color.android_orange_light),
|
||||
PorterDuff.Mode.SRC_ATOP);
|
||||
mStatusDivider.setVisibility(View.VISIBLE);
|
||||
mStatusLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mStatusDivider.setVisibility(View.GONE);
|
||||
mStatusLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
@@ -27,10 +28,9 @@ import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -52,6 +53,8 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
private View mActionEditDivider;
|
||||
private View mActionEncrypt;
|
||||
private View mActionCertify;
|
||||
private View mActionCertifyText;
|
||||
private ImageView mActionCertifyImage;
|
||||
private View mActionCertifyDivider;
|
||||
|
||||
private ListView mUserIds;
|
||||
@@ -76,6 +79,11 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
|
||||
mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt);
|
||||
mActionCertify = view.findViewById(R.id.view_key_action_certify);
|
||||
mActionCertifyText = view.findViewById(R.id.view_key_action_certify_text);
|
||||
mActionCertifyImage = (ImageView) view.findViewById(R.id.view_key_action_certify_image);
|
||||
// make certify image gray, like action icons
|
||||
mActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
mActionCertifyDivider = view.findViewById(R.id.view_key_action_certify_divider);
|
||||
|
||||
return root;
|
||||
@@ -182,6 +190,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
|
||||
mActionEdit.setEnabled(false);
|
||||
mActionCertify.setEnabled(false);
|
||||
mActionCertifyText.setEnabled(false);
|
||||
mActionEncrypt.setEnabled(false);
|
||||
} else {
|
||||
mActionEdit.setEnabled(true);
|
||||
@@ -189,9 +198,11 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
|
||||
mActionCertify.setEnabled(false);
|
||||
mActionCertifyText.setEnabled(false);
|
||||
mActionEncrypt.setEnabled(false);
|
||||
} else {
|
||||
mActionCertify.setEnabled(true);
|
||||
mActionCertifyText.setEnabled(true);
|
||||
mActionEncrypt.setEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -225,7 +236,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
private void encrypt(Uri dataUri) {
|
||||
// If there is no encryption key, don't bother.
|
||||
if (!mHasEncrypt) {
|
||||
AppMsg.makeText(getActivity(), R.string.error_no_encrypt_subkey, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_no_encrypt_subkey, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -246,15 +257,13 @@ public class ViewKeyMainFragment extends LoaderFragment implements
|
||||
private void certify(Uri dataUri) {
|
||||
Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class);
|
||||
signIntent.setData(dataUri);
|
||||
startActivity(signIntent);
|
||||
startActivityForResult(signIntent, 0);
|
||||
}
|
||||
|
||||
private void editKey(Uri dataUri) {
|
||||
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
|
||||
// editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
// startActivityForResult(editIntent, 0);
|
||||
startActivity(editIntent);
|
||||
startActivityForResult(editIntent, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,14 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
@@ -33,8 +40,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@@ -45,8 +50,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -152,7 +157,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
|
||||
if(!toClipboard){
|
||||
if (!toClipboard) {
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
} else {
|
||||
content = fingerprint;
|
||||
@@ -171,13 +176,13 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
} else {
|
||||
message = getResources().getString(R.string.key_copied_to_clipboard);
|
||||
}
|
||||
AppMsg.makeText(getActivity(), message, AppMsg.STYLE_INFO).show();
|
||||
Notify.showNotify(getActivity(), message, Notify.Style.OK);
|
||||
} else {
|
||||
// Android will fail with android.os.TransactionTooLargeException if key is too big
|
||||
// see http://www.lonestarprod.com/?p=34
|
||||
if (content.length() >= 86389) {
|
||||
AppMsg.makeText(getActivity(), R.string.key_too_big_for_sharing,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.key_too_big_for_sharing,
|
||||
Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,19 +200,20 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "error processing key!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_key_processing, Notify.Style.ERROR);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error processing key!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_key_processing, Notify.Style.ERROR);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
Notify.showNotify(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void showQrCodeDialog() {
|
||||
ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(mDataUri);
|
||||
dialog.show(ViewKeyShareFragment.this.getActivity().getSupportFragmentManager(), "shareQrCodeDialog");
|
||||
Intent qrCodeIntent = new Intent(getActivity(), QrCodeActivity.class);
|
||||
qrCodeIntent.setData(mDataUri);
|
||||
startActivity(qrCodeIntent);
|
||||
}
|
||||
|
||||
private void showNfcHelpDialog() {
|
||||
@@ -292,10 +298,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
|
||||
mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
|
||||
|
||||
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
mFingerprintQrCode.setImageBitmap(
|
||||
QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE)
|
||||
);
|
||||
loadQrCode(fingerprint);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -311,4 +314,35 @@ public class ViewKeyShareFragment extends LoaderFragment implements
|
||||
*/
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load QR Code asynchronously and with a fade in animation
|
||||
*
|
||||
* @param fingerprint
|
||||
*/
|
||||
private void loadQrCode(final String fingerprint) {
|
||||
AsyncTask<Void, Void, Bitmap> loadTask =
|
||||
new AsyncTask<Void, Void, Bitmap>() {
|
||||
protected Bitmap doInBackground(Void... unused) {
|
||||
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
return QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE);
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap qrCode) {
|
||||
mFingerprintQrCode.setImageBitmap(qrCode);
|
||||
|
||||
// Transition drawable with a transparent drawable and the final bitmap
|
||||
final TransitionDrawable td =
|
||||
new TransitionDrawable(new Drawable[]{
|
||||
new ColorDrawable(Color.TRANSPARENT),
|
||||
new BitmapDrawable(getResources(), qrCode)
|
||||
});
|
||||
|
||||
mFingerprintQrCode.setImageDrawable(td);
|
||||
td.startTransition(200);
|
||||
}
|
||||
};
|
||||
|
||||
loadTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
|
||||
public class WizardActivity extends ActionBarActivity {
|
||||
|
||||
private State mCurrentState;
|
||||
|
||||
// values for mCurrentScreen
|
||||
private enum State {
|
||||
START, CREATE_KEY, IMPORT_KEY, K9
|
||||
}
|
||||
|
||||
public static final int REQUEST_CODE_IMPORT = 0x00007703;
|
||||
|
||||
Button mBackButton;
|
||||
Button mNextButton;
|
||||
StartFragment mStartFragment;
|
||||
CreateKeyFragment mCreateKeyFragment;
|
||||
K9Fragment mK9Fragment;
|
||||
|
||||
private static final String K9_PACKAGE = "com.fsck.k9";
|
||||
// private static final String K9_MARKET_INTENT_URI_BASE = "market://details?id=%s";
|
||||
// private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
|
||||
// String.format(K9_MARKET_INTENT_URI_BASE, K9_PACKAGE)));
|
||||
private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/k9mail/k-9/releases/tag/4.904"));
|
||||
|
||||
LinearLayout mProgressLayout;
|
||||
View mProgressLine;
|
||||
ProgressBar mProgressBar;
|
||||
ImageView mProgressImage;
|
||||
TextView mProgressText;
|
||||
|
||||
/**
|
||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
||||
* set and the EditText gets the focus.
|
||||
*
|
||||
* @param context
|
||||
* @param editText
|
||||
* @return true if EditText is not empty
|
||||
*/
|
||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
||||
boolean output = true;
|
||||
if (editText.getText().toString().length() == 0) {
|
||||
editText.setError("empty!");
|
||||
editText.requestFocus();
|
||||
output = false;
|
||||
} else {
|
||||
editText.setError(null);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static class StartFragment extends Fragment {
|
||||
public static StartFragment newInstance() {
|
||||
StartFragment myFragment = new StartFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
myFragment.setArguments(args);
|
||||
|
||||
return myFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.wizard_start_fragment,
|
||||
container, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CreateKeyFragment extends Fragment {
|
||||
public static CreateKeyFragment newInstance() {
|
||||
CreateKeyFragment myFragment = new CreateKeyFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
myFragment.setArguments(args);
|
||||
|
||||
return myFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.wizard_create_key_fragment,
|
||||
container, false);
|
||||
|
||||
final AutoCompleteTextView emailView = (AutoCompleteTextView) view.findViewById(R.id.email);
|
||||
emailView.setThreshold(1); // Start working from first character
|
||||
emailView.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(getActivity())
|
||||
)
|
||||
);
|
||||
emailView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String email = editable.toString();
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email);
|
||||
if (emailMatcher.matches()) {
|
||||
emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_ok, 0);
|
||||
} else {
|
||||
emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.uid_mail_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
final AutoCompleteTextView nameView = (AutoCompleteTextView) view.findViewById(R.id.name);
|
||||
nameView.setThreshold(1); // Start working from first character
|
||||
nameView.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(getActivity())
|
||||
)
|
||||
);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
public static class K9Fragment extends Fragment {
|
||||
public static K9Fragment newInstance() {
|
||||
K9Fragment myFragment = new K9Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
myFragment.setArguments(args);
|
||||
|
||||
return myFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.wizard_k9_fragment,
|
||||
container, false);
|
||||
|
||||
HtmlTextView text = (HtmlTextView) v
|
||||
.findViewById(R.id.wizard_k9_text);
|
||||
text.setHtmlFromString("Install K9. It's good for you! Here is a screenhot how to enable OK in K9: (TODO)", true);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads new fragment
|
||||
*
|
||||
* @param fragment
|
||||
*/
|
||||
private void loadFragment(Fragment fragment) {
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager
|
||||
.beginTransaction();
|
||||
fragmentTransaction.replace(R.id.wizard_container,
|
||||
fragment);
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate View and initialize fragments for this Activity
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.wizard_activity);
|
||||
mBackButton = (Button) findViewById(R.id.wizard_back);
|
||||
mNextButton = (Button) findViewById(R.id.wizard_next);
|
||||
|
||||
// progress layout
|
||||
mProgressLayout = (LinearLayout) findViewById(R.id.wizard_progress);
|
||||
mProgressLine = findViewById(R.id.wizard_progress_line);
|
||||
mProgressBar = (ProgressBar) findViewById(R.id.wizard_progress_progressbar);
|
||||
mProgressImage = (ImageView) findViewById(R.id.wizard_progress_image);
|
||||
mProgressText = (TextView) findViewById(R.id.wizard_progress_text);
|
||||
|
||||
changeToState(State.START);
|
||||
}
|
||||
|
||||
private enum ProgressState {
|
||||
WORKING, ENABLED, DISABLED, ERROR
|
||||
}
|
||||
|
||||
private void showProgress(ProgressState state, String text) {
|
||||
switch (state) {
|
||||
case WORKING:
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressImage.setVisibility(View.GONE);
|
||||
break;
|
||||
case ENABLED:
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressImage.setVisibility(View.VISIBLE);
|
||||
// mProgressImage.setImageDrawable(getResources().getDrawable(
|
||||
// R.drawable.status_enabled));
|
||||
break;
|
||||
case DISABLED:
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressImage.setVisibility(View.VISIBLE);
|
||||
// mProgressImage.setImageDrawable(getResources().getDrawable(
|
||||
// R.drawable.status_disabled));
|
||||
break;
|
||||
case ERROR:
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
mProgressImage.setVisibility(View.VISIBLE);
|
||||
// mProgressImage.setImageDrawable(getResources().getDrawable(
|
||||
// R.drawable.status_fail));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mProgressText.setText(text);
|
||||
|
||||
mProgressLine.setVisibility(View.VISIBLE);
|
||||
mProgressLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideProgress() {
|
||||
mProgressLine.setVisibility(View.GONE);
|
||||
mProgressLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void nextOnClick(View view) {
|
||||
// close keyboard
|
||||
if (getCurrentFocus() != null) {
|
||||
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.hideSoftInputFromWindow(getCurrentFocus()
|
||||
.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
}
|
||||
|
||||
switch (mCurrentState) {
|
||||
case START: {
|
||||
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_start_radio_group);
|
||||
int selectedId = radioGroup.getCheckedRadioButtonId();
|
||||
switch (selectedId) {
|
||||
case R.id.wizard_start_new_key: {
|
||||
changeToState(State.CREATE_KEY);
|
||||
break;
|
||||
}
|
||||
case R.id.wizard_start_import: {
|
||||
changeToState(State.IMPORT_KEY);
|
||||
break;
|
||||
}
|
||||
case R.id.wizard_start_skip: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mBackButton.setText(R.string.btn_back);
|
||||
break;
|
||||
}
|
||||
case CREATE_KEY:
|
||||
EditText nameEdit = (EditText) findViewById(R.id.name);
|
||||
EditText emailEdit = (EditText) findViewById(R.id.email);
|
||||
EditText passphraseEdit = (EditText) findViewById(R.id.passphrase);
|
||||
|
||||
if (isEditTextNotEmpty(this, nameEdit)
|
||||
&& isEditTextNotEmpty(this, emailEdit)
|
||||
&& isEditTextNotEmpty(this, passphraseEdit)) {
|
||||
|
||||
// SaveKeyringParcel newKey = new SaveKeyringParcel();
|
||||
// newKey.addUserIds.add(nameEdit.getText().toString() + " <"
|
||||
// + emailEdit.getText().toString() + ">");
|
||||
|
||||
|
||||
AsyncTask<String, Boolean, Boolean> generateTask = new AsyncTask<String, Boolean, Boolean>() {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
showProgress(ProgressState.WORKING, "generating key...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result) {
|
||||
showProgress(ProgressState.ENABLED, "key generated successfully!");
|
||||
|
||||
changeToState(State.K9);
|
||||
} else {
|
||||
showProgress(ProgressState.ERROR, "error in key gen");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
generateTask.execute("");
|
||||
}
|
||||
break;
|
||||
case K9: {
|
||||
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_k9_radio_group);
|
||||
int selectedId = radioGroup.getCheckedRadioButtonId();
|
||||
switch (selectedId) {
|
||||
case R.id.wizard_k9_install: {
|
||||
try {
|
||||
startActivity(K9_MARKET_INTENT);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Activity not found for: " + K9_MARKET_INTENT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R.id.wizard_k9_skip: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_IMPORT: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// imported now...
|
||||
changeToState(State.K9);
|
||||
} else {
|
||||
// back to start
|
||||
changeToState(State.START);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void backOnClick(View view) {
|
||||
switch (mCurrentState) {
|
||||
case START:
|
||||
finish();
|
||||
break;
|
||||
case CREATE_KEY:
|
||||
changeToState(State.START);
|
||||
break;
|
||||
case IMPORT_KEY:
|
||||
changeToState(State.START);
|
||||
break;
|
||||
default:
|
||||
changeToState(State.START);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void changeToState(State state) {
|
||||
switch (state) {
|
||||
case START: {
|
||||
mCurrentState = State.START;
|
||||
mStartFragment = StartFragment.newInstance();
|
||||
loadFragment(mStartFragment);
|
||||
mBackButton.setText(android.R.string.cancel);
|
||||
mNextButton.setText(R.string.btn_next);
|
||||
break;
|
||||
}
|
||||
case CREATE_KEY: {
|
||||
mCurrentState = State.CREATE_KEY;
|
||||
mCreateKeyFragment = CreateKeyFragment.newInstance();
|
||||
loadFragment(mCreateKeyFragment);
|
||||
break;
|
||||
}
|
||||
case IMPORT_KEY: {
|
||||
mCurrentState = State.IMPORT_KEY;
|
||||
Intent intent = new Intent(this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intent, REQUEST_CODE_IMPORT);
|
||||
break;
|
||||
}
|
||||
case K9: {
|
||||
mCurrentState = State.K9;
|
||||
mBackButton.setEnabled(false); // don't go back to import/create key
|
||||
mK9Fragment = K9Fragment.newInstance();
|
||||
loadFragment(mK9Fragment);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,20 +30,21 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ImportKeysListLoader
|
||||
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
|
||||
public static class FileHasNoContent extends Exception {
|
||||
|
||||
public static class NoValidKeysException extends Exception {
|
||||
}
|
||||
|
||||
public static class NonPgpPart extends Exception {
|
||||
public static class NonPgpPartException extends Exception {
|
||||
private int mCount;
|
||||
|
||||
public NonPgpPart(int count) {
|
||||
public NonPgpPartException(int count) {
|
||||
this.mCount = count;
|
||||
}
|
||||
|
||||
@@ -67,7 +68,6 @@ public class ImportKeysListLoader
|
||||
|
||||
@Override
|
||||
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
|
||||
|
||||
// This has already been loaded! nvm any further, just return
|
||||
if (mEntryListWrapper != null) {
|
||||
return mEntryListWrapper;
|
||||
@@ -119,9 +119,6 @@ public class ImportKeysListLoader
|
||||
* @return
|
||||
*/
|
||||
private void generateListOfKeyrings(InputData inputData) {
|
||||
|
||||
boolean isEmpty = true;
|
||||
|
||||
PositionAwareInputStream progressIn = new PositionAwareInputStream(
|
||||
inputData.getInputStream());
|
||||
|
||||
@@ -130,27 +127,23 @@ public class ImportKeysListLoader
|
||||
// armor blocks
|
||||
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
|
||||
try {
|
||||
|
||||
// read all available blocks... (asc files can contain many blocks with BEGIN END)
|
||||
while (bufferedInput.available() > 0) {
|
||||
// TODO: deal with non-keyring objects?
|
||||
List<UncachedKeyRing> rings = UncachedKeyRing.fromStream(bufferedInput);
|
||||
for(UncachedKeyRing key : rings) {
|
||||
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), key);
|
||||
mData.add(item);
|
||||
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(key.getEncoded()));
|
||||
isEmpty = false;
|
||||
}
|
||||
// parse all keyrings
|
||||
Iterator<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput);
|
||||
while (it.hasNext()) {
|
||||
UncachedKeyRing ring = it.next();
|
||||
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring);
|
||||
mData.add(item);
|
||||
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Exception on parsing key file!", e);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e);
|
||||
|
||||
if (isEmpty) {
|
||||
Log.e(Constants.TAG, "File has no content!", new FileHasNoContent());
|
||||
NoValidKeysException e1 = new NoValidKeysException();
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>
|
||||
(mData, new FileHasNoContent());
|
||||
(mData, e1);
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Other Exception on parsing key file!", e);
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,15 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class SubkeysAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
|
||||
private boolean hasAnySecret;
|
||||
|
||||
private ColorStateList mDefaultTextColor;
|
||||
|
||||
public static final String[] SUBKEYS_PROJECTION = new String[]{
|
||||
@@ -71,10 +72,21 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
private static final int INDEX_EXPIRY = 11;
|
||||
private static final int INDEX_FINGERPRINT = 12;
|
||||
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags) {
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags,
|
||||
SaveKeyringParcel saveKeyringParcel) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mSaveKeyringParcel = saveKeyringParcel;
|
||||
}
|
||||
|
||||
public SubkeysAdapter(Context context, Cursor c, int flags) {
|
||||
this(context, c, flags, null);
|
||||
}
|
||||
|
||||
public long getKeyId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(INDEX_KEY_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,79 +106,94 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
TextView keyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
|
||||
ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
ImageView revokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
|
||||
TextView vKeyId = (TextView) view.findViewById(R.id.keyId);
|
||||
TextView vKeyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
TextView vKeyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
|
||||
ImageView vMasterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
ImageView vCertifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||
ImageView vEncryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
ImageView vSignIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
ImageView vRevokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
|
||||
ImageView vEditImage = (ImageView) view.findViewById(R.id.edit_image);
|
||||
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(INDEX_KEY_ID));
|
||||
long keyId = cursor.getLong(INDEX_KEY_ID);
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||
context,
|
||||
cursor.getInt(INDEX_ALGORITHM),
|
||||
cursor.getInt(INDEX_KEY_SIZE)
|
||||
);
|
||||
|
||||
keyId.setText(keyIdStr);
|
||||
vKeyId.setText(keyIdStr);
|
||||
// may be set with additional "stripped" later on
|
||||
if (hasAnySecret && cursor.getInt(INDEX_HAS_SECRET) == 0) {
|
||||
keyDetails.setText(algorithmStr + ", " +
|
||||
vKeyDetails.setText(algorithmStr + ", " +
|
||||
context.getString(R.string.key_stripped));
|
||||
} else {
|
||||
keyDetails.setText(algorithmStr);
|
||||
vKeyDetails.setText(algorithmStr);
|
||||
}
|
||||
|
||||
// Set icons according to properties
|
||||
masterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
certifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
|
||||
encryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE);
|
||||
signIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE);
|
||||
vMasterKeyIcon.setVisibility(cursor.getInt(INDEX_RANK) == 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE);
|
||||
vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE);
|
||||
vSignIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean valid = true;
|
||||
if (cursor.getInt(INDEX_IS_REVOKED) > 0) {
|
||||
revokedKeyIcon.setVisibility(View.VISIBLE);
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
|
||||
valid = false;
|
||||
// for edit key
|
||||
if (mSaveKeyringParcel != null) {
|
||||
boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));
|
||||
|
||||
if (revokeThisSubkey) {
|
||||
if (!isRevoked) {
|
||||
isRevoked = true;
|
||||
}
|
||||
}
|
||||
|
||||
vEditImage.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
keyId.setTextColor(mDefaultTextColor);
|
||||
keyDetails.setTextColor(mDefaultTextColor);
|
||||
keyExpiry.setTextColor(mDefaultTextColor);
|
||||
|
||||
revokedKeyIcon.setVisibility(View.GONE);
|
||||
vEditImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (isRevoked) {
|
||||
vRevokedKeyIcon.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vKeyId.setTextColor(mDefaultTextColor);
|
||||
vKeyDetails.setTextColor(mDefaultTextColor);
|
||||
vKeyExpiry.setTextColor(mDefaultTextColor);
|
||||
|
||||
vRevokedKeyIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean isExpired;
|
||||
if (!cursor.isNull(INDEX_EXPIRY)) {
|
||||
Date expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000);
|
||||
isExpired = expiryDate.before(new Date());
|
||||
|
||||
valid = valid && expiryDate.after(new Date());
|
||||
keyExpiry.setText(
|
||||
context.getString(R.string.label_expiry) + ": " +
|
||||
DateFormat.getDateFormat(context).format(expiryDate)
|
||||
);
|
||||
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": "
|
||||
+ DateFormat.getDateFormat(context).format(expiryDate));
|
||||
} else {
|
||||
keyExpiry.setText(
|
||||
context.getString(R.string.label_expiry) + ": " +
|
||||
context.getString(R.string.none)
|
||||
);
|
||||
isExpired = false;
|
||||
|
||||
vKeyExpiry.setText(context.getString(R.string.label_expiry) + ": " + context.getString(R.string.none));
|
||||
}
|
||||
|
||||
// if key is expired or revoked, strike through text
|
||||
if (!valid) {
|
||||
keyId.setText(OtherHelper.strikeOutText(keyId.getText()));
|
||||
keyDetails.setText(OtherHelper.strikeOutText(keyDetails.getText()));
|
||||
keyExpiry.setText(OtherHelper.strikeOutText(keyExpiry.getText()));
|
||||
boolean isInvalid = isRevoked || isExpired;
|
||||
if (isInvalid) {
|
||||
vKeyId.setText(OtherHelper.strikeOutText(vKeyId.getText()));
|
||||
vKeyDetails.setText(OtherHelper.strikeOutText(vKeyDetails.getText()));
|
||||
vKeyExpiry.setText(OtherHelper.strikeOutText(vKeyExpiry.getText()));
|
||||
}
|
||||
keyId.setEnabled(valid);
|
||||
keyDetails.setEnabled(valid);
|
||||
keyExpiry.setEnabled(valid);
|
||||
vKeyId.setEnabled(!isInvalid);
|
||||
vKeyDetails.setEnabled(!isInvalid);
|
||||
vKeyExpiry.setEnabled(!isInvalid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.view_key_keys_item, null);
|
||||
View view = mInflater.inflate(R.layout.view_key_subkey_item, null);
|
||||
if (mDefaultTextColor == null) {
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
mDefaultTextColor = keyId.getTextColors();
|
||||
@@ -177,13 +204,21 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
// Disable selection of items, http://stackoverflow.com/a/4075045
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
if (mSaveKeyringParcel == null) {
|
||||
return false;
|
||||
} else {
|
||||
return super.areAllItemsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable selection of items, http://stackoverflow.com/a/4075045
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return false;
|
||||
if (mSaveKeyringParcel == null) {
|
||||
return false;
|
||||
} else {
|
||||
return super.isEnabled(position);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -40,9 +42,7 @@ import java.util.ArrayList;
|
||||
|
||||
public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemClickListener {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private final ArrayList<Boolean> mCheckStates;
|
||||
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
|
||||
public static final String[] USER_IDS_PROJECTION = new String[]{
|
||||
@@ -60,7 +60,6 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
private static final int INDEX_IS_PRIMARY = 4;
|
||||
private static final int INDEX_IS_REVOKED = 5;
|
||||
|
||||
|
||||
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes,
|
||||
SaveKeyringParcel saveKeyringParcel) {
|
||||
super(context, c, flags);
|
||||
@@ -134,15 +133,18 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
// for edit key
|
||||
if (mSaveKeyringParcel != null) {
|
||||
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null);
|
||||
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId));
|
||||
boolean revokeThisUserId = (mSaveKeyringParcel.revokeUserIds.contains(userId));
|
||||
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null);
|
||||
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId));
|
||||
boolean revokeThisUserId = (mSaveKeyringParcel.mRevokeUserIds.contains(userId));
|
||||
|
||||
// only if primary user id will be changed
|
||||
// (this is not triggered if the user id is currently the primary one)
|
||||
if (changeAnyPrimaryUserId) {
|
||||
// change all user ids, only this one should be primary
|
||||
// change _all_ primary user ids and set new one to true
|
||||
isPrimary = changeThisPrimaryUserId;
|
||||
}
|
||||
|
||||
if (revokeThisUserId) {
|
||||
if (!isRevoked) {
|
||||
isRevoked = true;
|
||||
@@ -156,7 +158,10 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
if (isRevoked) {
|
||||
// set revocation icon (can this even be primary?)
|
||||
vVerified.setImageResource(R.drawable.key_certify_revoke);
|
||||
vVerified.setImageResource(R.drawable.status_signature_revoked_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.bg_gray),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
// disable and strike through text for revoked user ids
|
||||
vName.setEnabled(false);
|
||||
@@ -170,22 +175,33 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
vAddress.setEnabled(true);
|
||||
vComment.setEnabled(true);
|
||||
|
||||
// verified: has been verified
|
||||
// isPrimary: show small star icon for primary user ids
|
||||
int verified = cursor.getInt(INDEX_VERIFIED);
|
||||
switch (verified) {
|
||||
if (isPrimary) {
|
||||
vName.setTypeface(null, Typeface.BOLD);
|
||||
vAddress.setTypeface(null, Typeface.BOLD);
|
||||
} else {
|
||||
vName.setTypeface(null, Typeface.NORMAL);
|
||||
vAddress.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
|
||||
int isVerified = cursor.getInt(INDEX_VERIFIED);
|
||||
switch (isVerified) {
|
||||
case Certs.VERIFIED_SECRET:
|
||||
vVerified.setImageResource(isPrimary
|
||||
? R.drawable.key_certify_primary_ok_depth0
|
||||
: R.drawable.key_certify_ok_depth0);
|
||||
vVerified.setImageResource(R.drawable.status_signature_verified_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.android_green_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
break;
|
||||
case Certs.VERIFIED_SELF:
|
||||
vVerified.setImageResource(isPrimary
|
||||
? R.drawable.key_certify_primary_ok_self
|
||||
: R.drawable.key_certify_ok_self);
|
||||
vVerified.setImageResource(R.drawable.status_signature_unverified_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.bg_gray),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
break;
|
||||
default:
|
||||
vVerified.setImageResource(R.drawable.key_certify_error);
|
||||
vVerified.setImageResource(R.drawable.status_signature_invalid_cutout);
|
||||
vVerified.setColorFilter(
|
||||
mContext.getResources().getColor(R.color.android_red_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -233,7 +249,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.view_key_userids_item, null);
|
||||
View view = mInflater.inflate(R.layout.view_key_user_id_item, null);
|
||||
// only need to do this once ever, since mShowCheckBoxes is final
|
||||
view.findViewById(R.id.checkBox).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE);
|
||||
return view;
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.ui.dialog;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class ChangeExpiryDialogFragment extends DialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_CREATION_DATE = "creation_date";
|
||||
private static final String ARG_EXPIRY_DATE = "expiry_date";
|
||||
|
||||
public static final int MESSAGE_NEW_EXPIRY_DATE = 1;
|
||||
public static final String MESSAGE_DATA_EXPIRY_DATE = "expiry_date";
|
||||
|
||||
private Messenger mMessenger;
|
||||
private Calendar mCreationCal;
|
||||
private Calendar mExpiryCal;
|
||||
|
||||
private int mDatePickerResultCount = 0;
|
||||
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener =
|
||||
new DatePickerDialog.OnDateSetListener() {
|
||||
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||
// Note: Ignore results after the first one - android sends multiples.
|
||||
if (mDatePickerResultCount++ == 0) {
|
||||
Calendar selectedCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
selectedCal.set(year, monthOfYear, dayOfMonth);
|
||||
if (mExpiryCal != null) {
|
||||
long numDays = (selectedCal.getTimeInMillis() / 86400000)
|
||||
- (mExpiryCal.getTimeInMillis() / 86400000);
|
||||
if (numDays > 0) {
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime());
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
} else {
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, selectedCal.getTime());
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public class ExpiryDatePickerDialog extends DatePickerDialog {
|
||||
|
||||
public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack,
|
||||
int year, int monthOfYear, int dayOfMonth) {
|
||||
super(context, callBack, year, monthOfYear, dayOfMonth);
|
||||
}
|
||||
|
||||
// set permanent title
|
||||
public void setTitle(CharSequence title) {
|
||||
super.setTitle(getContext().getString(R.string.expiry_date_dialog_title));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*/
|
||||
public static ChangeExpiryDialogFragment newInstance(Messenger messenger,
|
||||
Date creationDate, Date expiryDate) {
|
||||
ChangeExpiryDialogFragment frag = new ChangeExpiryDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putSerializable(ARG_CREATION_DATE, creationDate);
|
||||
args.putSerializable(ARG_EXPIRY_DATE, expiryDate);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
Date creationDate = (Date) getArguments().getSerializable(ARG_CREATION_DATE);
|
||||
Date expiryDate = (Date) getArguments().getSerializable(ARG_EXPIRY_DATE);
|
||||
|
||||
mCreationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
mCreationCal.setTime(creationDate);
|
||||
mExpiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
mExpiryCal.setTime(expiryDate);
|
||||
|
||||
/*
|
||||
* Using custom DatePickerDialog which overrides the setTitle because
|
||||
* the DatePickerDialog title is buggy (unix warparound bug).
|
||||
* See: https://code.google.com/p/android/issues/detail?id=49066
|
||||
*/
|
||||
DatePickerDialog dialog = new ExpiryDatePickerDialog(getActivity(),
|
||||
mExpiryDateSetListener, mExpiryCal.get(Calendar.YEAR), mExpiryCal.get(Calendar.MONTH),
|
||||
mExpiryCal.get(Calendar.DAY_OF_MONTH));
|
||||
mDatePickerResultCount = 0;
|
||||
dialog.setCancelable(true);
|
||||
dialog.setButton(Dialog.BUTTON_NEGATIVE,
|
||||
getActivity().getString(R.string.btn_no_date),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Note: Ignore results after the first one - android sends multiples.
|
||||
if (mDatePickerResultCount++ == 0) {
|
||||
// none expiry dates corresponds to a null message
|
||||
Bundle data = new Bundle();
|
||||
data.putSerializable(MESSAGE_DATA_EXPIRY_DATE, null);
|
||||
sendMessageToHandler(MESSAGE_NEW_EXPIRY_DATE, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// setCalendarViewShown() is supported from API 11 onwards.
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Hide calendarView in tablets because of the unix warparound bug.
|
||||
dialog.getDatePicker().setCalendarViewShown(false);
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
// will crash with IllegalArgumentException if we set a min date
|
||||
// that is not before expiry
|
||||
if (mCreationCal != null && mCreationCal.before(mExpiryCal)) {
|
||||
dialog.getDatePicker().setMinDate(mCreationCal.getTime().getTime()
|
||||
+ DateUtils.DAY_IN_MILLIS);
|
||||
} else {
|
||||
// When created date isn't available
|
||||
dialog.getDatePicker().setMinDate(mExpiryCal.getTime().getTime()
|
||||
+ DateUtils.DAY_IN_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class EditSubkeyDialogFragment extends DialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
|
||||
public static final int MESSAGE_CHANGE_EXPIRY = 1;
|
||||
public static final int MESSAGE_REVOKE = 2;
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*/
|
||||
public static EditSubkeyDialogFragment newInstance(Messenger messenger) {
|
||||
EditSubkeyDialogFragment frag = new EditSubkeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity());
|
||||
CharSequence[] array = getResources().getStringArray(R.array.edit_key_edit_subkey);
|
||||
|
||||
builder.setTitle(R.string.edit_key_edit_subkey_title);
|
||||
builder.setItems(array, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case 0:
|
||||
sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null);
|
||||
break;
|
||||
case 1:
|
||||
sendMessageToHandler(MESSAGE_REVOKE, null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ import android.support.v4.app.FragmentActivity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
@@ -44,8 +44,8 @@ import android.widget.Toast;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
@@ -62,7 +62,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
private Messenger mMessenger;
|
||||
private EditText mPassphraseEditText;
|
||||
private boolean mCanKB;
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
@@ -102,10 +101,10 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
// check if secret key has a passphrase
|
||||
if (!(secretKeyId == Constants.key.symmetric || secretKeyId == Constants.key.none)) {
|
||||
try {
|
||||
if (!new ProviderHelper(context).getWrappedSecretKeyRing(secretKeyId).hasPassphrase()) {
|
||||
if (!new ProviderHelper(context).getCanonicalizedSecretKeyRing(secretKeyId).hasPassphrase()) {
|
||||
throw new PgpGeneralException("No passphrase! No passphrase dialog needed!");
|
||||
}
|
||||
} catch(ProviderHelper.NotFoundException e) {
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
throw new PgpGeneralException("Error: Key not found!", e);
|
||||
}
|
||||
}
|
||||
@@ -120,11 +119,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@@ -138,7 +132,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
alert.setTitle(R.string.title_authentication);
|
||||
|
||||
final WrappedSecretKeyRing secretRing;
|
||||
final CanonicalizedSecretKeyRing secretRing;
|
||||
String userId;
|
||||
|
||||
if (secretKeyId == Constants.key.symmetric || secretKeyId == Constants.key.none) {
|
||||
@@ -147,12 +141,12 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
} else {
|
||||
try {
|
||||
ProviderHelper helper = new ProviderHelper(activity);
|
||||
secretRing = helper.getWrappedSecretKeyRing(secretKeyId);
|
||||
secretRing = helper.getCanonicalizedSecretKeyRing(secretKeyId);
|
||||
// yes the inner try/catch block is necessary, otherwise the final variable
|
||||
// above can't be statically verified to have been set in all cases because
|
||||
// the catch clause doesn't return.
|
||||
try {
|
||||
userId = secretRing.getPrimaryUserId();
|
||||
userId = secretRing.getPrimaryUserIdWithFallback();
|
||||
} catch (PgpGeneralException e) {
|
||||
userId = null;
|
||||
}
|
||||
@@ -165,7 +159,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
mCanKB = false;
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@@ -189,7 +182,8 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
// Early breakout if we are dealing with a symmetric key
|
||||
if (secretRing == null) {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, Constants.key.symmetric, passphrase);
|
||||
PassphraseCacheService.addCachedPassphrase(activity, Constants.key.symmetric,
|
||||
passphrase, getString(R.string.passp_cache_notif_pwd));
|
||||
// also return passphrase back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_DATA_PASSPHRASE, passphrase);
|
||||
@@ -197,9 +191,9 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
return;
|
||||
}
|
||||
|
||||
WrappedSecretKey unlockedSecretKey = null;
|
||||
CanonicalizedSecretKey unlockedSecretKey = null;
|
||||
|
||||
for(WrappedSecretKey clickSecretKey : secretRing.secretKeyIterator()) {
|
||||
for (CanonicalizedSecretKey clickSecretKey : secretRing.secretKeyIterator()) {
|
||||
try {
|
||||
boolean unlocked = clickSecretKey.unlock(passphrase);
|
||||
if (unlocked) {
|
||||
@@ -228,10 +222,18 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
|
||||
// cache the new passphrase
|
||||
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
|
||||
PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase);
|
||||
|
||||
try {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase,
|
||||
secretRing.getPrimaryUserIdWithFallback());
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "adding of a passphrase failed", e);
|
||||
}
|
||||
|
||||
if (unlockedSecretKey.getKeyId() != masterKeyId) {
|
||||
PassphraseCacheService.addCachedPassphrase(
|
||||
activity, unlockedSecretKey.getKeyId(), passphrase);
|
||||
activity, unlockedSecretKey.getKeyId(), passphrase,
|
||||
unlockedSecretKey.getPrimaryUserIdWithFallback());
|
||||
}
|
||||
|
||||
// also return passphrase back to activity
|
||||
@@ -249,22 +251,32 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
}
|
||||
});
|
||||
|
||||
mCanKB = true;
|
||||
// Hack to open keyboard.
|
||||
// This is the only method that I found to work across all Android versions
|
||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||
// * opening in onActivityCreated does not work on Android 4.4
|
||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
mPassphraseEditText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mPassphraseEditText.requestFocus();
|
||||
|
||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
|
||||
return alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
if (mCanKB) {
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
super.onCancel(dialog);
|
||||
@@ -273,6 +285,27 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
Log.d(Constants.TAG, "onDismiss");
|
||||
|
||||
// hide keyboard on dismiss
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
//check if no view has focus:
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.dialog;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
@@ -32,6 +33,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
@@ -164,18 +166,50 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
|
||||
}
|
||||
});
|
||||
|
||||
// Hack to open keyboard.
|
||||
// This is the only method that I found to work across all Android versions
|
||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||
// * opening in onActivityCreated does not work on Android 4.4
|
||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
mPassphraseEditText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mPassphraseEditText.requestFocus();
|
||||
|
||||
mPassphraseAgainEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseAgainEditText.setOnEditorActionListener(this);
|
||||
|
||||
return alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
// hide keyboard on dismiss
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
mPassphraseAgainEditText.setOnEditorActionListener(this);
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
//check if no view has focus:
|
||||
View v = getActivity().getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* 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.ui.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
public class ShareQrCodeDialogFragment extends DialogFragment {
|
||||
private static final String ARG_KEY_URI = "uri";
|
||||
|
||||
private ImageView mImage;
|
||||
private TextView mText;
|
||||
|
||||
private static final int QR_CODE_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*/
|
||||
public static ShareQrCodeDialogFragment newInstance(Uri dataUri) {
|
||||
ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_KEY_URI, dataUri);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_KEY_URI);
|
||||
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(getActivity());
|
||||
alert.setTitle(R.string.share_qr_code_dialog_title);
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.share_qr_code_dialog, null);
|
||||
alert.setView(view);
|
||||
|
||||
mImage = (ImageView) view.findViewById(R.id.share_qr_code_dialog_image);
|
||||
mText = (TextView) view.findViewById(R.id.share_qr_code_dialog_text);
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(getActivity());
|
||||
String content;
|
||||
try {
|
||||
alert.setPositiveButton(R.string.btn_okay, null);
|
||||
|
||||
byte[] blob = (byte[]) providerHelper.getGenericData(
|
||||
KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if (blob == null) {
|
||||
Log.e(Constants.TAG, "key not found!");
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
return null;
|
||||
}
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
setQrCode(content);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
return null;
|
||||
}
|
||||
|
||||
return alert.show();
|
||||
}
|
||||
|
||||
private void setQrCode(String data) {
|
||||
mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(data, QR_CODE_SIZE));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import com.github.johnpersano.supertoasts.SuperToast;
|
||||
*/
|
||||
public class Notify {
|
||||
|
||||
public static enum Style {OK, WARN, ERROR}
|
||||
public static enum Style {OK, WARN, INFO, ERROR}
|
||||
|
||||
/**
|
||||
* Shows a simple in-layout notification with the CharSequence given as parameter
|
||||
|
||||
@@ -39,15 +39,21 @@ public class ProgressScaler implements Progressable {
|
||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||
*/
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
mWrapped.setProgress(resourceId, progress, mMax);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(resourceId, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
mWrapped.setProgress(progress, max);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="-100%"
|
||||
android:toXDelta="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="100%"
|
||||
android:toXDelta="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="-100%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set>
|
||||
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="100%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:duration="500" />
|
||||
</set>
|
||||
16
OpenKeychain/src/main/res/anim/qr_code_zoom_enter.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator">
|
||||
<scale
|
||||
android:fromXScale="0.5"
|
||||
android:toXScale="1.0"
|
||||
android:fromYScale="0.5"
|
||||
android:toYScale="1.0"
|
||||
android:pivotX="50%p"
|
||||
android:pivotY="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
<alpha
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
17
OpenKeychain/src/main/res/anim/qr_code_zoom_exit.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:zAdjustment="top">
|
||||
<scale
|
||||
android:fromXScale="1.0"
|
||||
android:toXScale="0.5"
|
||||
android:fromYScale="1.0"
|
||||
android:toYScale="0.5"
|
||||
android:pivotX="50%p"
|
||||
android:pivotY="50%p"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
<alpha
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
BIN
OpenKeychain/src/main/res/drawable-hdpi/create_key_robot.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 900 B |
|
Before Width: | Height: | Size: 757 B |
|
Before Width: | Height: | Size: 1.2 KiB |
BIN
OpenKeychain/src/main/res/drawable-hdpi/status_lock_closed.png
Normal file
|
After Width: | Height: | Size: 675 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/status_lock_error.png
Normal file
|
After Width: | Height: | Size: 748 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/status_lock_open.png
Normal file
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 723 B |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 528 B |
|
After Width: | Height: | Size: 444 B |
|
After Width: | Height: | Size: 714 B |
|
After Width: | Height: | Size: 861 B |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 740 B |
|
After Width: | Height: | Size: 536 B |
|
After Width: | Height: | Size: 813 B |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 652 B |
|
Before Width: | Height: | Size: 646 B |