diff options
Diffstat (limited to 'OpenPGP-Keychain/src/main/java')
22 files changed, 856 insertions, 383 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 011cd9663..ff4abe56a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -19,6 +19,11 @@ package org.sufficientlysecure.keychain;  import android.os.Environment;  import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity; +import org.sufficientlysecure.keychain.ui.DecryptActivity; +import org.sufficientlysecure.keychain.ui.EncryptActivity; +import org.sufficientlysecure.keychain.ui.ImportKeysActivity; +import org.sufficientlysecure.keychain.ui.KeyListActivity;  public final class Constants { @@ -63,4 +68,13 @@ public final class Constants {          public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu";      } +    public static final class DrawerItems { +        public static final Class KEY_LIST = KeyListActivity.class; +        public static final Class ENCRYPT = EncryptActivity.class; +        public static final Class DECRYPT = DecryptActivity.class; +        public static final Class IMPORT_KEYS = ImportKeysActivity.class; +        public static final Class REGISTERED_APPS_LIST = RegisteredAppsListActivity.class; +        public static final Class[] ARRAY = new Class[]{KEY_LIST, ENCRYPT, DECRYPT, +                                                IMPORT_KEYS, REGISTERED_APPS_LIST}; +    }  } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java index 1d79edd43..784ec340e 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/Id.java @@ -119,6 +119,7 @@ public final class Id {          public static final int secret_key = 0x21070002;          public static final int user_id = 0x21070003;          public static final int key = 0x21070004; +        public static final int public_secret_key = 0x21070005;      }      public static final class choice { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java index 91e50637e..a26df556d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ActionBarHelper.java @@ -37,7 +37,6 @@ public class ActionBarHelper {       * @param activity       */      public static void setBackButton(ActionBarActivity activity) { -        // set actionbar without home button if called from another app          final ActionBar actionBar = activity.getSupportActionBar();          Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="                  + activity.getCallingPackage()); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java index 557d75dbf..03cf936ee 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -30,12 +30,18 @@ import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.Id;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.service.KeychainIntentService;  import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;  import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;  import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;  import org.sufficientlysecure.keychain.util.Log; +import java.lang.reflect.Array; +import java.security.Provider; +import java.util.ArrayList; +  public class ExportHelper {      protected FileDialogFragment mFileDialog;      protected String mExportFilename; @@ -62,8 +68,8 @@ public class ExportHelper {      /**       * Show dialog where to export keys       */ -    public void showExportKeysDialog(final long[] rowIds, final int keyType, -                                     final String exportFilename) { +    public void showExportKeysDialog(final long[] masterKeyIds, final int keyType, +                                     final String exportFilename, final String checkboxString) {          mExportFilename = exportFilename;          // Message is received after file is selected @@ -72,9 +78,14 @@ public class ExportHelper {              public void handleMessage(Message message) {                  if (message.what == FileDialogFragment.MESSAGE_OKAY) {                      Bundle data = message.getData(); +                    int type = keyType;                      mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); -                    exportKeys(rowIds, keyType); +                    if( data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED) ) { +                        type = Id.type.public_secret_key; +                    } + +                    exportKeys(masterKeyIds, type);                  }              }          }; @@ -85,7 +96,7 @@ public class ExportHelper {          DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {              public void run() {                  String title = null; -                if (rowIds == null) { +                if (masterKeyIds == null) {                      // export all keys                      title = mActivity.getString(R.string.title_export_keys);                  } else { @@ -93,15 +104,10 @@ public class ExportHelper {                      title = mActivity.getString(R.string.title_export_key);                  } -                String message = null; -                if (keyType == Id.type.public_key) { -                    message = mActivity.getString(R.string.specify_file_to_export_to); -                } else { -                    message = mActivity.getString(R.string.specify_file_to_export_secret_keys_to); -                } +                String message = mActivity.getString(R.string.specify_file_to_export_to);                  mFileDialog = FileDialogFragment.newInstance(messenger, title, message, -                        exportFilename, null); +                        exportFilename, checkboxString);                  mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");              } @@ -111,7 +117,7 @@ public class ExportHelper {      /**       * Export keys       */ -    public void exportKeys(long[] rowIds, int keyType) { +    public void exportKeys(long[] masterKeyIds, int keyType) {          Log.d(Constants.TAG, "exportKeys started");          // Send all information needed to service to export key in other thread @@ -125,10 +131,10 @@ public class ExportHelper {          data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);          data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); -        if (rowIds == null) { +        if (masterKeyIds == null) {              data.putBoolean(KeychainIntentService.EXPORT_ALL, true);          } else { -            data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_ROW_ID, rowIds); +            data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);          }          intent.putExtra(KeychainIntentService.EXTRA_DATA, data); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java index eb46a52e5..736bff02d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java @@ -17,7 +17,12 @@  package org.sufficientlysecure.keychain.helper; +import android.graphics.Color;  import android.os.Bundle; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.util.Log; @@ -60,6 +65,63 @@ public class OtherHelper {          }      } +    public static SpannableStringBuilder colorizeFingerprint(String fingerprint) { +        SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint); +        try { +            // for each 4 characters of the fingerprint + 1 space +            for (int i = 0; i < fingerprint.length(); i += 5) { +                int spanEnd = Math.min(i + 4, fingerprint.length()); +                String fourChars = fingerprint.substring(i, spanEnd); + +                int raw = Integer.parseInt(fourChars, 16); +                byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)}; +                int[] color = OtherHelper.getRgbForData(bytes); +                int r = color[0]; +                int g = color[1]; +                int b = color[2]; + +                // we cannot change black by multiplication, so adjust it to an almost-black grey, +                // which will then be brightened to the minimal brightness level +                if (r == 0 && g == 0 && b == 0) { +                    r = 1; +                    g = 1; +                    b = 1; +                } + +                // Convert rgb to brightness +                double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; + +                // If a color is too dark to be seen on black, +                // then brighten it up to a minimal brightness. +                if (brightness < 80) { +                    double factor = 80.0 / brightness; +                    r = Math.min(255, (int) (r * factor)); +                    g = Math.min(255, (int) (g * factor)); +                    b = Math.min(255, (int) (b * factor)); + +                    // If it is too light, then darken it to a respective maximal brightness. +                } else if (brightness > 180) { +                    double factor = 180.0 / brightness; +                    r = (int) (r * factor); +                    g = (int) (g * factor); +                    b = (int) (b * factor); +                } + +                // Create a foreground color with the 3 digest integers as RGB +                // and then converting that int to hex to use as a color +                sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)), +                        i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); +            } +        } catch (Exception e) { +            Log.e(Constants.TAG, "Colorization failed", e); +            // if anything goes wrong, then just display the fingerprint without colour, +            // instead of partially correct colour or wrong colours +            return new SpannableStringBuilder(fingerprint); +        } + +        return sb; +    } +      /**       * Converts the given bytes to a unique RGB color using SHA1 algorithm       * diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index dbfa521e5..0e0fdec83 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;  import java.io.ByteArrayOutputStream;  import java.io.IOException;  import java.io.OutputStream; +import java.security.Provider;  import java.util.ArrayList;  import java.util.List; @@ -158,60 +159,68 @@ public class PgpImportExport {          return returnData;      } -    public Bundle exportKeyRings(ArrayList<Long> keyRingRowIds, int keyType, +    public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds, ArrayList<Long> secretKeyRingMasterIds,                                   OutputStream outStream) throws PgpGeneralException,              PGPException, IOException {          Bundle returnData = new Bundle(); -        int rowIdsSize = keyRingRowIds.size(); +        int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size(); +        int progress = 0;          updateProgress(                  mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, -                        rowIdsSize), 0, 100); +                        masterKeyIdsSize), 0, 100);          if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {              throw new PgpGeneralException(                      mContext.getString(R.string.error_external_storage_not_ready));          } -        // For each row id -        for (int i = 0; i < rowIdsSize; ++i) { +        // For each public masterKey id +        for (long pubKeyMasterId : publicKeyRingMasterIds) { +            progress++;              // Create an output stream              ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);              arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); -            // If the keyType is secret get the PGPSecretKeyRing -            // based on the row id and encode it to the output -            if (keyType == Id.type.secret_key) { -                updateProgress(i * 100 / rowIdsSize / 2, 100); -                PGPSecretKeyRing secretKeyRing = -                        ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i)); +            updateProgress(progress * 100 / masterKeyIdsSize, 100); +            PGPPublicKeyRing publicKeyRing = +                    ProviderHelper.getPGPPublicKeyRingByMasterKeyId(mContext, pubKeyMasterId); -                if (secretKeyRing != null) { -                    secretKeyRing.encode(arOutStream); -                } -                if (mKeychainServiceListener.hasServiceStopped()) { -                    arOutStream.close(); -                    return null; -                } -            } else { -                updateProgress(i * 100 / rowIdsSize, 100); -                PGPPublicKeyRing publicKeyRing = -                        ProviderHelper.getPGPPublicKeyRingByRowId(mContext, keyRingRowIds.get(i)); +            if (publicKeyRing != null) { +                publicKeyRing.encode(arOutStream); +            } -                if (publicKeyRing != null) { -                    publicKeyRing.encode(arOutStream); -                } +            if (mKeychainServiceListener.hasServiceStopped()) { +                arOutStream.close(); +                return null; +            } -                if (mKeychainServiceListener.hasServiceStopped()) { -                    arOutStream.close(); -                    return null; -                } +            arOutStream.close(); +        } + +        // For each secret masterKey id +        for (long secretKeyMasterId : secretKeyRingMasterIds) { +            progress++; +            // Create an output stream +            ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream); +            arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext)); + +            updateProgress(progress * 100 / masterKeyIdsSize, 100); +            PGPSecretKeyRing secretKeyRing = +                    ProviderHelper.getPGPSecretKeyRingByMasterKeyId(mContext, secretKeyMasterId); + +            if (secretKeyRing != null) { +                secretKeyRing.encode(arOutStream); +            } +            if (mKeychainServiceListener.hasServiceStopped()) { +                arOutStream.close(); +                return null;              }              arOutStream.close();          } -        returnData.putInt(KeychainIntentService.RESULT_EXPORT, rowIdsSize); +        returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize);          updateProgress(R.string.progress_done, 100, 100); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index 436c26700..b93c68677 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -477,7 +477,7 @@ public class PgpKeyHelper {       * @return       */      public static String convertKeyIdToHex(long keyId) { -        return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId); +        return "0x" + ((keyId >> 32) > 0 ? convertKeyIdToHex32bit(keyId >> 32) : "") + convertKeyIdToHex32bit(keyId);      }      private static String convertKeyIdToHex32bit(long keyId) { @@ -498,7 +498,7 @@ public class PgpKeyHelper {          int len = hexString.length();          String s2 = hexString.substring(len - 8);          String s1 = hexString.substring(0, len - 8); -        return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); +        return ((!s1.isEmpty() ? Long.parseLong(s1, 16) << 32 : 0) | Long.parseLong(s2, 16));      }      /** diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index c7ec02d7d..40a0b72ce 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.Id;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;  import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.util.IterableIterator;  import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.Primes;  import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; @@ -46,6 +47,8 @@ import java.security.*;  import java.util.ArrayList;  import java.util.Date;  import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List;  import java.util.TimeZone;  public class PgpKeyOperation { @@ -198,7 +201,7 @@ public class PgpKeyOperation {      public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,                                 ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates, -                               long masterKeyId, String oldPassPhrase, +                               PGPPublicKey oldPublicKey, String oldPassPhrase,                                 String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,              PGPException, NoSuchAlgorithmException, SignatureException, IOException { @@ -215,131 +218,166 @@ public class PgpKeyOperation {          updateProgress(R.string.progress_preparing_master_key, 10, 100); -        int usageId = keysUsages.get(0); -        boolean canSign = -                (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); -        boolean canEncrypt = -                (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); - -        String mainUserId = userIds.get(0); +        // prepare keyring generator with given master public and secret key +        PGPKeyRingGenerator keyGen; +        PGPPublicKey masterPublicKey; { + +            String mainUserId = userIds.get(0); + +            // prepare the master key pair +            PGPKeyPair masterKeyPair; { + +                PGPSecretKey masterKey = keys.get(0); + +                // this removes all userIds and certifications previously attached to the masterPublicKey +                PGPPublicKey tmpKey = masterKey.getPublicKey(); +                masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), +                        tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); + +                // already done by code above: +                // PGPPublicKey masterPublicKey = masterKey.getPublicKey(); +                // // Somehow, the PGPPublicKey already has an empty certification attached to it when the +                // // keyRing is generated the first time, we remove that when it exists, before adding the +                // new +                // // ones +                // PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey, +                // ""); +                // if (masterPublicKeyRmCert != null) { +                // masterPublicKey = masterPublicKeyRmCert; +                // } + +                PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( +                        Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()); +                PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); + +                updateProgress(R.string.progress_certifying_master_key, 20, 100); + +                // re-add old certificates, or create new ones for new uids +                for (String userId : userIds) { +                    // re-add certs for this uid, take a note if self-signed cert is in there +                    boolean foundSelfSign = false; +                    Iterator<PGPSignature> it = tmpKey.getSignaturesForID(userId); +                    if(it != null) for(PGPSignature sig : new IterableIterator<PGPSignature>(it)) { +                        if(sig.getKeyID() == masterPublicKey.getKeyID()) { +                            // already have a self sign? skip this other one, then. +                            // note: PGPKeyRingGenerator adds one cert for the main user id, which +                            // will lead to duplicates. unfortunately, if we add any other here +                            // first, that will change the main user id order... +                            if(foundSelfSign) +                                continue; +                            foundSelfSign = true; +                        } +                        Log.d(Constants.TAG, "adding old sig for " + userId + " from " +                                + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID())); +                        masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, sig); +                    } + +                    // there was an old self-signed certificate for this uid +                    if(foundSelfSign) +                        continue; + +                    Log.d(Constants.TAG, "generating self-signed cert for " + userId); + +                    PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( +                            masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) +                            .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); +                    PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + +                    sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + +                    PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); + +                    masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); +                } -        PGPSecretKey masterKey = keys.get(0); +                masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); +            } -        // this removes all userIds and certifications previously attached to the masterPublicKey -        PGPPublicKey tmpKey = masterKey.getPublicKey(); -        PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), -                tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); +            PGPSignatureSubpacketGenerator hashedPacketsGen; +            PGPSignatureSubpacketGenerator unhashedPacketsGen; { -        // already done by code above: -        // PGPPublicKey masterPublicKey = masterKey.getPublicKey(); -        // // Somehow, the PGPPublicKey already has an empty certification attached to it when the -        // // keyRing is generated the first time, we remove that when it exists, before adding the -        // new -        // // ones -        // PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey, -        // ""); -        // if (masterPublicKeyRmCert != null) { -        // masterPublicKey = masterPublicKeyRmCert; -        // } +                hashedPacketsGen = new PGPSignatureSubpacketGenerator(); +                unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); -        PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( -                Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()); -        PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); +                int usageId = keysUsages.get(0); +                boolean canEncrypt = +                        (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); -        updateProgress(R.string.progress_certifying_master_key, 20, 100); +                int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; +                if (canEncrypt) { +                    keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; +                } +                hashedPacketsGen.setKeyFlags(true, keyFlags); + +                hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); +                hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); +                hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); + +                if (keysExpiryDates.get(0) != null) { +                    GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); +                    creationDate.setTime(masterPublicKey.getCreationTime()); +                    GregorianCalendar expiryDate = keysExpiryDates.get(0); +                    //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c +                    //here we purposefully ignore partial days in each date - long type has no fractional part! +                    long numDays = +                            (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); +                    if (numDays <= 0) { +                        throw new PgpGeneralException( +                                mContext.getString(R.string.error_expiry_must_come_after_creation)); +                    } +                    hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); +                } else { +                    //do this explicitly, although since we're rebuilding, +                    hashedPacketsGen.setKeyExpirationTime(false, 0); +                    //this happens anyway +                } +            } -        // TODO: if we are editing a key, keep old certs, don't remake certs we don't have to. +            updateProgress(R.string.progress_building_master_key, 30, 100); -        for (String userId : userIds) { -            PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( -                    masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1) -                    .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); -            PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); +            // define hashing and signing algos +            PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( +                    HashAlgorithmTags.SHA1); +            PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder( +                    masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); -            sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); +            // Build key encrypter based on passphrase +            PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( +                    PGPEncryptedData.CAST5, sha1Calc) +                    .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( +                            newPassPhrase.toCharArray()); -            PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); +            keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, +                    masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), +                    unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); -            masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);          } -        PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); - -        PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); -        PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - -        int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; -        if (canEncrypt) { -            keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; -        } -        hashedPacketsGen.setKeyFlags(true, keyFlags); - -        hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); -        hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); -        hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); - -        if (keysExpiryDates.get(0) != null) { -            GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC")); -            creationDate.setTime(masterPublicKey.getCreationTime()); -            GregorianCalendar expiryDate = keysExpiryDates.get(0); -            //note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c -            //here we purposefully ignore partial days in each date - long type has no fractional part! -            long numDays = -                    (expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000); -            if (numDays <= 0) { -                throw new PgpGeneralException( -                        mContext.getString(R.string.error_expiry_must_come_after_creation)); -            } -            hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400); -        } else { -            //do this explicitly, although since we're rebuilding, -            hashedPacketsGen.setKeyExpirationTime(false, 0); -            //this happens anyway -        } - -        updateProgress(R.string.progress_building_master_key, 30, 100); - -        // define hashing and signing algos -        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( -                HashAlgorithmTags.SHA1); -        PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder( -                masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); - -        // Build key encrypter based on passphrase -        PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( -                PGPEncryptedData.CAST5, sha1Calc) -                .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( -                        newPassPhrase.toCharArray()); - -        PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, -                masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), -                unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); -          updateProgress(R.string.progress_adding_sub_keys, 40, 100);          for (int i = 1; i < keys.size(); ++i) { -            updateProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100); +            updateProgress(40 + 40 * (i - 1) / (keys.size() - 1), 100);              PGPSecretKey subKey = keys.get(i);              PGPPublicKey subPublicKey = subKey.getPublicKey(); -            PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() +            PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()                      .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(                              oldPassPhrase.toCharArray()); -            PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2); +            PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor);              // TODO: now used without algorithm and creation time?! (APG 1)              PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); -            hashedPacketsGen = new PGPSignatureSubpacketGenerator(); -            unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); +            PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); +            PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); -            keyFlags = 0; +            int keyFlags = 0; -            usageId = keysUsages.get(i); -            canSign = +            int usageId = keysUsages.get(i); +            boolean canSign =                      (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); -            canEncrypt = +            boolean canEncrypt =                      (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);              if (canSign) {                  Date todayDate = new Date(); //both sig times the same @@ -388,53 +426,99 @@ public class PgpKeyOperation {          PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();          PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); +        updateProgress(R.string.progress_re_adding_certs, 80, 100); + +        // re-add certificates from old public key +        // TODO: this only takes care of user id certificates, what about others? +        PGPPublicKey pubkey = publicKeyRing.getPublicKey(); +        for(String uid : new IterableIterator<String>(pubkey.getUserIDs())) { +            for(PGPSignature sig : new IterableIterator<PGPSignature>(oldPublicKey.getSignaturesForID(uid), true)) { +                // but skip self certificates +                if(sig.getKeyID() == pubkey.getKeyID()) +                    continue; +                pubkey = PGPPublicKey.addCertification(pubkey, uid, sig); +            } +        } +        publicKeyRing = PGPPublicKeyRing.insertPublicKey(publicKeyRing, pubkey); +          updateProgress(R.string.progress_saving_key_ring, 90, 100); +        /* additional handy debug info +        Log.d(Constants.TAG, " ------- in private key -------"); +        for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) { +            for(PGPSignature sig : new IterableIterator<PGPSignature>(secretKeyRing.getPublicKey().getSignaturesForID(uid))) { +                Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); +            } +        } +        Log.d(Constants.TAG, " ------- in public key -------"); +        for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) { +            for(PGPSignature sig : new IterableIterator<PGPSignature>(publicKeyRing.getPublicKey().getSignaturesForID(uid))) { +                Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid); +            } +        } +        */ +          ProviderHelper.saveKeyRing(mContext, secretKeyRing);          ProviderHelper.saveKeyRing(mContext, publicKeyRing);          updateProgress(R.string.progress_done, 100, 100);      } -    public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, String passphrase) +    /** +     * Certify the given pubkeyid with the given masterkeyid. +     * +     * @param masterKeyId Certifying key, must be available as secret key +     * @param pubKeyId ID of public key to certify +     * @param userIds User IDs to certify, must not be null or empty +     * @param passphrase Passphrase of the secret key +     * @return A keyring with added certifications +     */ +    public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)              throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,              PGPException, SignatureException {          if (passphrase == null) {              throw new PgpGeneralException("Unable to obtain passphrase");          } else { -            PGPPublicKeyRing pubring = ProviderHelper -                    .getPGPPublicKeyRingByKeyId(mContext, pubKeyId); -            PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); -            if (certificationKey == null) { -                throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); -            } +            // create a signatureGenerator from the supplied masterKeyId and passphrase +            PGPSignatureGenerator signatureGenerator; { -            PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( -                    Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); -            PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); -            if (signaturePrivateKey == null) { -                throw new PgpGeneralException( -                        mContext.getString(R.string.error_could_not_extract_private_key)); -            } - -            // TODO: SHA256 fixed? -            JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( -                    certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) -                    .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); +                PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId); +                if (certificationKey == null) { +                    throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed)); +                } -            PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( -                    contentSignerBuilder); +                PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( +                        Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); +                PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor); +                if (signaturePrivateKey == null) { +                    throw new PgpGeneralException( +                            mContext.getString(R.string.error_could_not_extract_private_key)); +                } -            signatureGenerator.init(PGPSignature.DIRECT_KEY, signaturePrivateKey); +                // TODO: SHA256 fixed? +                JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder( +                        certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256) +                        .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); -            PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); +                signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); +                signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey); +            } -            PGPSignatureSubpacketVector packetVector = spGen.generate(); -            signatureGenerator.setHashedSubpackets(packetVector); +            { // supply signatureGenerator with a SubpacketVector +                PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); +                PGPSignatureSubpacketVector packetVector = spGen.generate(); +                signatureGenerator.setHashedSubpackets(packetVector); +            } -            PGPPublicKey signedKey = PGPPublicKey.addCertification(pubring.getPublicKey(pubKeyId), -                    signatureGenerator.generate()); +            // fetch public key ring, add the certification and return it +            PGPPublicKeyRing pubring = ProviderHelper +                    .getPGPPublicKeyRingByKeyId(mContext, pubKeyId); +            PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId); +            for(String userId : new IterableIterator<String>(userIds.iterator())) { +                PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey); +                signedKey = PGPPublicKey.addCertification(signedKey, userId, sig); +            }              pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);              return pubring; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 748aaeb1b..b963ceb39 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -55,6 +55,7 @@ public class KeychainProvider extends ContentProvider {      private static final int PUBLIC_KEY_RING_USER_ID = 121;      private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122; +    private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID = 123;      private static final int SECRET_KEY_RING = 201;      private static final int SECRET_KEY_RING_BY_ROW_ID = 202; @@ -150,6 +151,7 @@ public class KeychainProvider extends ContentProvider {           * <pre>           * key_rings/public/#/user_ids           * key_rings/public/#/user_ids/# +         * key_rings/public/master_key_id/#/user_ids           * </pre>           */          matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" @@ -158,6 +160,10 @@ public class KeychainProvider extends ContentProvider {          matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"                  + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#",                  PUBLIC_KEY_RING_USER_ID_BY_ROW_ID); +        matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" +                + KeychainContract.PATH_PUBLIC + "/" +                + KeychainContract.PATH_BY_MASTER_KEY_ID + "/*/" + KeychainContract.PATH_USER_IDS, +                PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID);          /**           * secret key rings @@ -285,6 +291,7 @@ public class KeychainProvider extends ContentProvider {                  return Keys.CONTENT_ITEM_TYPE;              case PUBLIC_KEY_RING_USER_ID: +            case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:              case SECRET_KEY_RING_USER_ID:                  return UserIds.CONTENT_TYPE; @@ -322,6 +329,7 @@ public class KeychainProvider extends ContentProvider {              case PUBLIC_KEY_RING_KEY:              case PUBLIC_KEY_RING_KEY_BY_ROW_ID:              case PUBLIC_KEY_RING_USER_ID: +            case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID:              case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:                  type = KeyTypes.PUBLIC;                  break; @@ -364,6 +372,11 @@ public class KeychainProvider extends ContentProvider {          // TODO: deprecated master key id          //projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEYS + "." + KeysColumns.KEY_ID); +        projectionMap.put(KeysColumns.ALGORITHM, Tables.KEYS + "." + KeysColumns.ALGORITHM); +        projectionMap.put(KeysColumns.KEY_SIZE, Tables.KEYS + "." + KeysColumns.KEY_SIZE); +        projectionMap.put(KeysColumns.CREATION, Tables.KEYS + "." + KeysColumns.CREATION); +        projectionMap.put(KeysColumns.EXPIRY, Tables.KEYS + "." + KeysColumns.EXPIRY); +        projectionMap.put(KeysColumns.KEY_RING_ROW_ID, Tables.KEYS + "." + KeysColumns.KEY_RING_ROW_ID);          projectionMap.put(KeysColumns.FINGERPRINT, Tables.KEYS + "." + KeysColumns.FINGERPRINT);          projectionMap.put(KeysColumns.IS_REVOKED, Tables.KEYS + "." + KeysColumns.IS_REVOKED); @@ -403,6 +416,18 @@ public class KeychainProvider extends ContentProvider {          return projectionMap;      } +    private HashMap<String, String> getProjectionMapForUserIds() { +        HashMap<String, String> projectionMap = new HashMap<String, String>(); + +        projectionMap.put(BaseColumns._ID, Tables.USER_IDS + "." + BaseColumns._ID); +        projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); +        projectionMap.put(UserIdsColumns.RANK, Tables.USER_IDS + "." + UserIdsColumns.RANK); +        projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." +                + KeyRingsColumns.MASTER_KEY_ID); + +        return projectionMap; +    } +      /**       * Builds default query for keyRings: KeyRings table is joined with UserIds and Keys       */ @@ -606,6 +631,17 @@ public class KeychainProvider extends ContentProvider {                  break; +            case PUBLIC_KEY_RING_BY_MASTER_KEY_ID_USER_ID: +                qb.setTables(Tables.USER_IDS + " INNER JOIN " + Tables.KEY_RINGS + " ON " + "(" +                        + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "." +                        + KeysColumns.KEY_RING_ROW_ID + " )"); +                qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = "); +                qb.appendWhereEscapeString(uri.getPathSegments().get(3)); + +                qb.setProjectionMap(getProjectionMapForUserIds()); + +                break; +              case PUBLIC_KEY_RING_USER_ID:              case SECRET_KEY_RING_USER_ID:                  qb.setTables(Tables.USER_IDS); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 4e5812202..6ec6dcf8a 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -131,7 +131,6 @@ public class KeychainIntentService extends IntentService      public static final String EXPORT_KEY_TYPE = "export_key_type";      public static final String EXPORT_ALL = "export_all";      public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; -    public static final String EXPORT_KEY_RING_ROW_ID = "export_key_rind_row_id";      // upload key      public static final String UPLOAD_KEY_SERVER = "upload_key_server"; @@ -143,6 +142,7 @@ public class KeychainIntentService extends IntentService      // sign key      public static final String CERTIFY_KEY_MASTER_KEY_ID = "sign_key_master_key_id";      public static final String CERTIFY_KEY_PUB_KEY_ID = "sign_key_pub_key_id"; +    public static final String CERTIFY_KEY_UIDS = "sign_key_uids";      /*       * possible data keys as result send over messenger @@ -542,8 +542,9 @@ public class KeychainIntentService extends IntentService                              ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),                              oldPassPhrase, newPassPhrase);                  } else { -                    keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates, masterKeyId, -                            oldPassPhrase, newPassPhrase); +                    PGPPublicKey pubkey = ProviderHelper.getPGPPublicKeyByKeyId(this, masterKeyId); +                    keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates, +                            pubkey, oldPassPhrase, newPassPhrase);                  }                  PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase); @@ -660,16 +661,11 @@ public class KeychainIntentService extends IntentService                  if (data.containsKey(EXPORT_KEY_TYPE)) {                      keyType = data.getInt(EXPORT_KEY_TYPE);                  } - +                long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);                  String outputFile = data.getString(EXPORT_FILENAME); -                long[] rowIds = new long[0]; - -                // If not exporting all keys get the rowIds of the keys to export from the intent +                // If not exporting all keys get the masterKeyIds of the keys to export from the intent                  boolean exportAll = data.getBoolean(EXPORT_ALL); -                if (!exportAll) { -                    rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID); -                }                  /* Operation */ @@ -678,30 +674,38 @@ public class KeychainIntentService extends IntentService                      throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));                  } -                // OutputStream -                FileOutputStream outStream = new FileOutputStream(outputFile); +                ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>(); +                ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>(); +                ArrayList<Long> allPublicMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this); +                ArrayList<Long> allSecretMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this); -                ArrayList<Long> keyRingRowIds = new ArrayList<Long>();                  if (exportAll) { - -                    // get all key ring row ids based on export type -                    if (keyType == Id.type.public_key) { -                        keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this); -                    } else { -                        keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this); +                    // get all public key ring MasterKey ids +                    if (keyType == Id.type.public_key || keyType == Id.type.public_secret_key) { +                        publicMasterKeyIds = allPublicMasterKeyIds; +                    } +                    // get all secret key ring MasterKey ids +                    if (keyType == Id.type.secret_key || keyType == Id.type.public_secret_key) { +                        secretMasterKeyIds = allSecretMasterKeyIds;                      }                  } else { -                    for (long rowId : rowIds) { -                        keyRingRowIds.add(rowId); + +                    for (long masterKeyId : masterKeyIds) { +                        if ((keyType == Id.type.public_key || keyType == Id.type.public_secret_key) +                                                                && allPublicMasterKeyIds.contains(masterKeyId)) { +                            publicMasterKeyIds.add(masterKeyId); +                        } +                        if ((keyType == Id.type.secret_key || keyType == Id.type.public_secret_key) +                                                                && allSecretMasterKeyIds.contains(masterKeyId)) { +                            secretMasterKeyIds.add(masterKeyId); +                        }                      }                  } -                Bundle resultData; -                  PgpImportExport pgpImportExport = new PgpImportExport(this, this, this); - -                resultData = pgpImportExport -                        .exportKeyRings(keyRingRowIds, keyType, outStream); +                Bundle resultData = pgpImportExport +                        .exportKeyRings(publicMasterKeyIds, secretMasterKeyIds, +                                new FileOutputStream(outputFile));                  if (mIsCanceled) {                      boolean isDeleted = new File(outputFile).delete(); @@ -804,6 +808,7 @@ public class KeychainIntentService extends IntentService                  /* Input */                  long masterKeyId = data.getLong(CERTIFY_KEY_MASTER_KEY_ID);                  long pubKeyId = data.getLong(CERTIFY_KEY_PUB_KEY_ID); +                ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);                  /* Operation */                  String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this, @@ -811,7 +816,7 @@ public class KeychainIntentService extends IntentService                  PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);                  PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId, -                        signaturePassPhrase); +                        userIds, signaturePassPhrase);                  // store the signed key in our local cache                  PgpImportExport pgpImportExport = new PgpImportExport(this, null); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index 9a9911c94..d8df5ced3 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -19,11 +19,15 @@ package org.sufficientlysecure.keychain.ui;  import android.app.ProgressDialog;  import android.content.Intent; +import android.database.Cursor;  import android.net.Uri;  import android.os.Bundle;  import android.os.Handler;  import android.os.Message;  import android.os.Messenger; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader;  import android.support.v7.app.ActionBar;  import android.support.v7.app.ActionBarActivity;  import android.view.View; @@ -32,26 +36,28 @@ import android.widget.*;  import android.widget.CompoundButton.OnCheckedChangeListener;  import com.beardedhen.androidbootstrap.BootstrapButton;  import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSignature;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.OtherHelper;  import org.sufficientlysecure.keychain.helper.Preferences;  import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;  import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.service.KeychainIntentService;  import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;  import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;  import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;  import org.sufficientlysecure.keychain.util.Log; -import java.util.Iterator; +import java.util.ArrayList;  /**   * Signs the specified public key with the specified secret master key   */  public class CertifyKeyActivity extends ActionBarActivity implements -        SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { +        SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {      private BootstrapButton mSignButton;      private CheckBox mUploadKeyCheckbox;      private Spinner mSelectKeyserverSpinner; @@ -62,6 +68,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements      private long mPubKeyId = 0;      private long mMasterKeyId = 0; +    private ListView mUserIds; +    private ViewKeyUserIdsAdapter mUserIdsAdapter; + +    private static final int LOADER_ID_KEYRING = 0; +    private static final int LOADER_ID_USER_IDS = 1; +      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); @@ -125,9 +137,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements              finish();              return;          } +        Log.e(Constants.TAG, "uri: " + mDataUri);          PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri); +        mUserIds = (ListView) findViewById(R.id.user_ids); + +        mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true); +        mUserIds.setAdapter(mUserIdsAdapter); + +        getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); +        getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); +          if (signKey != null) {              mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();          } @@ -138,6 +159,76 @@ public class CertifyKeyActivity extends ActionBarActivity implements          }      } +    static final String[] KEYRING_PROJECTION = +            new String[] { +                    KeychainContract.KeyRings._ID, +                    KeychainContract.KeyRings.MASTER_KEY_ID, +                    KeychainContract.Keys.FINGERPRINT, +                    KeychainContract.UserIds.USER_ID +            }; +    static final int INDEX_MASTER_KEY_ID = 1; +    static final int INDEX_FINGERPRINT = 2; +    static final int INDEX_USER_ID = 3; + +    static final String[] USER_IDS_PROJECTION = +            new String[]{ +                    KeychainContract.UserIds._ID, +                    KeychainContract.UserIds.USER_ID, +                    KeychainContract.UserIds.RANK +            }; +    static final String USER_IDS_SORT_ORDER = +            KeychainContract.UserIds.RANK + " ASC"; + +    @Override +    public Loader<Cursor> onCreateLoader(int id, Bundle args) { +        switch(id) { +            case LOADER_ID_KEYRING: +                return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null); +            case LOADER_ID_USER_IDS: { +                Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri); +                return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER); +            } +        } +        return null; +    } + +    @Override +    public void onLoadFinished(Loader<Cursor> loader, Cursor data) { +        switch(loader.getId()) { +            case LOADER_ID_KEYRING: +                // the first key here is our master key +                if (data.moveToFirst()) { +                    long keyId = data.getLong(INDEX_MASTER_KEY_ID); +                    String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId); +                    ((TextView) findViewById(R.id.key_id)).setText(keyIdStr); + +                    String mainUserId = data.getString(INDEX_USER_ID); +                    ((TextView) findViewById(R.id.main_user_id)).setText(mainUserId); + +                    byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT); +                    if (fingerprintBlob == null) { +                        // FALLBACK for old database entries +                        fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri); +                    } +                    String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); +                    ((TextView) findViewById(R.id.fingerprint)).setText(OtherHelper.colorizeFingerprint(fingerprint)); +                } +                break; +            case LOADER_ID_USER_IDS: +                mUserIdsAdapter.swapCursor(data); +                break; +        } +    } + +    @Override +    public void onLoaderReset(Loader<Cursor> loader) { +        switch(loader.getId()) { +            case LOADER_ID_USER_IDS: +                mUserIdsAdapter.swapCursor(null); +                break; +        } +    } +      private void showPassphraseDialog(final long secretKeyId) {          // Message is received after passphrase is cached          Handler returnHandler = new Handler() { @@ -173,6 +264,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements              // if we have already signed this key, dont bother doing it again              boolean alreadySigned = false; +            /* todo: reconsider this at a later point when certs are in the db              @SuppressWarnings("unchecked")              Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();              while (itr.hasNext()) { @@ -182,6 +274,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements                      break;                  }              } +            */              if (!alreadySigned) {                  /* @@ -209,6 +302,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements       * kicks off the actual signing process on a background thread       */      private void startSigning() { + +        // Bail out if there is not at least one user id selected +        ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds(); +        if(userIds.isEmpty()) { +            Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!", +                    Toast.LENGTH_SHORT).show(); +            return; +        } +          // Send all information needed to service to sign key in other thread          Intent intent = new Intent(this, KeychainIntentService.class); @@ -219,6 +321,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements          data.putLong(KeychainIntentService.CERTIFY_KEY_MASTER_KEY_ID, mMasterKeyId);          data.putLong(KeychainIntentService.CERTIFY_KEY_PUB_KEY_ID, mPubKeyId); +        data.putStringArrayList(KeychainIntentService.CERTIFY_KEY_UIDS, userIds);          intent.putExtra(KeychainIntentService.EXTRA_DATA, data); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java index c0fd53007..f01e67449 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -21,19 +21,17 @@ import android.app.Activity;  import android.content.Context;  import android.content.Intent;  import android.content.res.Configuration; +import android.graphics.Color;  import android.os.Bundle;  import android.support.v4.app.ActionBarDrawerToggle;  import android.support.v4.view.GravityCompat;  import android.support.v4.widget.DrawerLayout;  import android.support.v7.app.ActionBarActivity;  import android.view.*; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; +import android.widget.*;  import com.beardedhen.androidbootstrap.FontAwesomeText; +import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;  public class DrawerActivity extends ActionBarActivity {      private DrawerLayout mDrawerLayout; @@ -42,10 +40,8 @@ public class DrawerActivity extends ActionBarActivity {      private CharSequence mDrawerTitle;      private CharSequence mTitle; +    private boolean mIsDrawerLocked = false; -    private static Class[] mItemsClass = new Class[]{KeyListActivity.class, -            EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class, -            RegisteredAppsListActivity.class};      private Class mSelectedItem;      private static final int MENU_ID_PREFERENCE = 222; @@ -55,10 +51,22 @@ public class DrawerActivity extends ActionBarActivity {          mDrawerTitle = getString(R.string.app_name);          mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);          mDrawerList = (ListView) findViewById(R.id.left_drawer); - -        // set a custom shadow that overlays the main content when the drawer -        // opens -        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); +        ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame); +        int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin; +        int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size); +        int errorInMarginAllowed = 5; + +        // if the left margin of the loaded layout is close to the +        // one used in tablets then set drawer as open and locked +        if( Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) { +            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList); +            mDrawerLayout.setScrimColor(Color.TRANSPARENT); +            mIsDrawerLocked = true; +        } else { +            // set a custom shadow that overlays the main content when the drawer opens +            mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); +            mIsDrawerLocked = false; +        }          NavItem mItemIconTexts[] = new NavItem[]{                  new NavItem("fa-user", getString(R.string.nav_contacts)), @@ -73,8 +81,11 @@ public class DrawerActivity extends ActionBarActivity {          mDrawerList.setOnItemClickListener(new DrawerItemClickListener());          // enable ActionBar app icon to behave as action to toggle nav drawer -        getSupportActionBar().setDisplayHomeAsUpEnabled(true); -        getSupportActionBar().setHomeButtonEnabled(true); +        // if the drawer is not locked +        if ( !mIsDrawerLocked ) { +            getSupportActionBar().setDisplayHomeAsUpEnabled(true); +            getSupportActionBar().setHomeButtonEnabled(true); +        }          // ActionBarDrawerToggle ties together the the proper interactions          // between the sliding drawer and the action bar app icon @@ -86,19 +97,8 @@ public class DrawerActivity extends ActionBarActivity {          ) {              public void onDrawerClosed(View view) {                  getSupportActionBar().setTitle(mTitle); -                // creates call to onPrepareOptionsMenu() -                supportInvalidateOptionsMenu(); -                // call intent activity if selected -                if (mSelectedItem != null) { -                    finish(); -                    overridePendingTransition(0, 0); - -                    Intent intent = new Intent(DrawerActivity.this, mSelectedItem); -                    startActivity(intent); -                    // disable animation of activity start -                    overridePendingTransition(0, 0); -                } +                callIntentForDrawerItem(mSelectedItem);              }              public void onDrawerOpened(View drawerView) { @@ -108,13 +108,40 @@ public class DrawerActivity extends ActionBarActivity {                  supportInvalidateOptionsMenu();              }          }; -        mDrawerLayout.setDrawerListener(mDrawerToggle); +        if ( !mIsDrawerLocked ) { +            mDrawerLayout.setDrawerListener(mDrawerToggle); +        } else { +            // If the drawer is locked open make it un-focusable +            // so that it doesn't consume all the Back button presses +            mDrawerLayout.setFocusableInTouchMode(false); +        }          // if (savedInstanceState == null) {          // selectItem(0);          // }      } +    /** +     * Uses startActivity to call the Intent of the given class +     * @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.* +     */ +    public void callIntentForDrawerItem(Class drawerItem) { +        // creates call to onPrepareOptionsMenu() +        supportInvalidateOptionsMenu(); + +        // call intent activity if selected +        if (drawerItem != null) { +            finish(); +            overridePendingTransition(0, 0); + +            Intent intent = new Intent(this, drawerItem); +            startActivity(intent); + +            // disable animation of activity start +            overridePendingTransition(0, 0); +        } +    } +      @Override      public boolean onCreateOptionsMenu(Menu menu) {          menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences); @@ -185,10 +212,18 @@ public class DrawerActivity extends ActionBarActivity {      private void selectItem(int position) {          // update selected item and title, then close the drawer          mDrawerList.setItemChecked(position, true); -        // setTitle(mDrawerTitles[position]); -        mDrawerLayout.closeDrawer(mDrawerList);          // set selected class -        mSelectedItem = mItemsClass[position]; +        mSelectedItem = Constants.DrawerItems.ARRAY[position]; + +        // setTitle(mDrawerTitles[position]); +        // If drawer isn't locked just close the drawer and +        // it will move to the selected item by itself (via drawer toggle listener) +        if ( !mIsDrawerLocked ) { +            mDrawerLayout.closeDrawer(mDrawerList); +        // else move to the selected item yourself +        } else { +            callIntentForDrawerItem(mSelectedItem); +        }      }      /** diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index edf980773..31804719f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -322,8 +322,10 @@ public class EditKeyActivity extends ActionBarActivity {                  cancelClicked();                  return true;              case R.id.menu_key_edit_export_file: -                long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())}; -                mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC); +                long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri); +                long[] ids = new long[]{masterKeyId}; +                mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC, +                                                            null);                  return true;              case R.id.menu_key_edit_delete: {                  // Message is received after key is deleted @@ -368,9 +370,8 @@ public class EditKeyActivity extends ActionBarActivity {          }          mCurrentPassphrase = ""; - -        buildLayout();          mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); +        buildLayout();          if (!mIsPassPhraseSet) {              // check "no passphrase" checkbox and remove button              mNoPassphrase.setChecked(true); @@ -425,11 +426,14 @@ public class EditKeyActivity extends ActionBarActivity {          // find views          mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);          mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); -          // Build layout based on given userIds and keys +          LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);          LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container); +        if(mIsPassPhraseSet){ +            mChangePassphrase.setText(getString(R.string.btn_change_passphrase)); +        }          mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);          mUserIdsView.setType(Id.type.user_id);          mUserIdsView.setCanEdit(mMasterCanSign); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 078b998e1..06df6f12d 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -53,13 +53,11 @@ public class KeyListActivity extends DrawerActivity {      public boolean onOptionsItemSelected(MenuItem item) {          switch (item.getItemId()) {              case R.id.menu_key_list_import: -                Intent intentImport = new Intent(this, ImportKeysActivity.class); -                startActivityForResult(intentImport, 0); +                callIntentForDrawerItem(Constants.DrawerItems.IMPORT_KEYS);                  return true;              case R.id.menu_key_list_export: -                // TODO fix this for unified keylist -                mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB); +                mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB, null);                  return true;              case R.id.menu_key_list_create: @@ -71,7 +69,7 @@ public class KeyListActivity extends DrawerActivity {                  return true;              case R.id.menu_key_list_secret_export: -                mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC); +                mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC, null);                  return true;              default: diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 5ac59965d..cac8b7046 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -49,6 +49,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;  import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;  import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;  import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;  import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;  import org.sufficientlysecure.keychain.util.Log; @@ -183,13 +184,22 @@ public class KeyListFragment extends Fragment                              break;                          }                          case R.id.menu_key_list_multi_export: { -                            // todo: public/secret needs to be handled differently here                              ids = mStickyList.getWrappedList().getCheckedItemIds(); +                            long[] masterKeyIds = new long[2*ids.length]; +                            ArrayList<Long> allPubRowIds = +                                    ProviderHelper.getPublicKeyRingsRowIds(getActivity()); +                            for (int i = 0; i < ids.length; i++) { +                                if (allPubRowIds.contains(ids[i])) { +                                    masterKeyIds[i] = ProviderHelper.getPublicMasterKeyId(getActivity(), ids[i]); +                                } else { +                                    masterKeyIds[i] = ProviderHelper.getSecretMasterKeyId(getActivity(), ids[i]); +                                } +                            }                              ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());                              mExportHelper -                                    .showExportKeysDialog(ids, -                                            Id.type.public_key, -                                            Constants.Path.APP_DIR_FILE_PUB); +                                    .showExportKeysDialog(masterKeyIds, Id.type.public_key, +                                            Constants.Path.APP_DIR_FILE_PUB, +                                            getString(R.string.also_export_secret_keys));                              break;                          }                          case R.id.menu_key_list_multi_select_all: { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java index beff8385e..3d22ca9f6 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectSecretKeyLayoutFragment.java @@ -39,6 +39,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment {      private TextView mKeyUserId;      private TextView mKeyUserIdRest;      private TextView mKeyMasterKeyIdHex; +    private TextView mNoKeySelected;      private BootstrapButton mSelectKeyButton;      private Boolean mFilterCertify; @@ -60,10 +61,10 @@ public class SelectSecretKeyLayoutFragment extends Fragment {      public void selectKey(long secretKeyId) {          if (secretKeyId == Id.key.none) { -            mKeyMasterKeyIdHex.setText(R.string.api_settings_no_key); -            mKeyUserIdRest.setText(""); +            mNoKeySelected.setVisibility(View.VISIBLE);              mKeyUserId.setVisibility(View.GONE);              mKeyUserIdRest.setVisibility(View.GONE); +            mKeyMasterKeyIdHex.setVisibility(View.GONE);          } else {              PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( @@ -93,20 +94,25 @@ public class SelectSecretKeyLayoutFragment extends Fragment {                      mKeyMasterKeyIdHex.setText(masterkeyIdHex);                      mKeyUserId.setText(userName);                      mKeyUserIdRest.setText(userEmail); +                    mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);                      mKeyUserId.setVisibility(View.VISIBLE);                      mKeyUserIdRest.setVisibility(View.VISIBLE); +                    mNoKeySelected.setVisibility(View.GONE);                  } else { -                    mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_key)); +                    mKeyMasterKeyIdHex.setVisibility(View.GONE);                      mKeyUserId.setVisibility(View.GONE);                      mKeyUserIdRest.setVisibility(View.GONE); +                    mNoKeySelected.setVisibility(View.VISIBLE);                  }              } else {                  mKeyMasterKeyIdHex.setText(                          getActivity().getResources()                                  .getString(R.string.no_keys_added_or_updated)                           + " for master id: " + secretKeyId); +                mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);                  mKeyUserId.setVisibility(View.GONE);                  mKeyUserIdRest.setVisibility(View.GONE); +                mNoKeySelected.setVisibility(View.GONE);              }          } @@ -124,6 +130,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment {      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {          View view = inflater.inflate(R.layout.select_secret_key_layout_fragment, container, false); +        mNoKeySelected = (TextView) view.findViewById(R.id.no_key_selected);          mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);          mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);          mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 93bb83003..c4097403c 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -124,8 +124,11 @@ public class ViewKeyActivity extends ActionBarActivity {                  uploadToKeyserver(mDataUri);                  return true;              case R.id.menu_key_view_export_file: -                long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())}; -                mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB); +                long masterKeyId = +                        ProviderHelper.getPublicMasterKeyId(this, Long.valueOf(mDataUri.getLastPathSegment())); +                long[] ids = new long[]{masterKeyId}; +                mExportHelper.showExportKeysDialog(ids, Id.type.public_key, +                        Constants.Path.APP_DIR_FILE_PUB, null);                  return true;              case R.id.menu_key_view_share_default_fingerprint:                  shareKey(mDataUri, true); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java index 4844c4028..7dbf35a3f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java @@ -26,10 +26,7 @@ import android.support.v4.app.Fragment;  import android.support.v4.app.LoaderManager;  import android.support.v4.content.CursorLoader;  import android.support.v4.content.Loader; -import android.text.Spannable; -import android.text.SpannableStringBuilder;  import android.text.format.DateFormat; -import android.text.style.ForegroundColorSpan;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup; @@ -65,6 +62,7 @@ public class ViewKeyMainFragment extends Fragment implements      private TextView mSecretKey;      private BootstrapButton mActionEdit;      private BootstrapButton mActionEncrypt; +    private BootstrapButton mActionCertify;      private ListView mUserIds;      private ListView mKeys; @@ -95,6 +93,7 @@ public class ViewKeyMainFragment extends Fragment implements          mKeys = (ListView) view.findViewById(R.id.keys);          mActionEdit = (BootstrapButton) view.findViewById(R.id.action_edit);          mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt); +        mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify);          return view;      } @@ -131,6 +130,11 @@ public class ViewKeyMainFragment extends Fragment implements                  mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));                  mSecretKey.setText(R.string.secret_key_yes); +                // certify button +                // TODO this button MIGHT be useful if the user wants to +                // certify a private key with another... +                // mActionCertify.setVisibility(View.GONE); +                  // edit button                  mActionEdit.setVisibility(View.VISIBLE);                  mActionEdit.setOnClickListener(new View.OnClickListener() { @@ -147,8 +151,22 @@ public class ViewKeyMainFragment extends Fragment implements              } else {                  mSecretKey.setTextColor(Color.BLACK);                  mSecretKey.setText(getResources().getString(R.string.secret_key_no)); + +                // certify button +                mActionCertify.setVisibility(View.VISIBLE); +                // edit button                  mActionEdit.setVisibility(View.GONE);              } + +            // TODO see todo note above, doing this here for now +            mActionCertify.setOnClickListener(new View.OnClickListener() { +                public void onClick(View view) { +                    certifyKey(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri( +                            Long.toString(masterKeyId) +                    )); +                } +            }); +          }          mActionEncrypt.setOnClickListener(new View.OnClickListener() { @@ -180,12 +198,13 @@ public class ViewKeyMainFragment extends Fragment implements      static final int KEYRING_INDEX_USER_ID = 2;      static final String[] USER_IDS_PROJECTION = -            new String[]{KeychainContract.UserIds._ID, KeychainContract.UserIds.USER_ID, -            KeychainContract.UserIds.RANK, }; -    // not the main user id -    static final String USER_IDS_SELECTION = KeychainContract.UserIds.RANK + " > 0 "; +            new String[]{ +                KeychainContract.UserIds._ID, +                KeychainContract.UserIds.USER_ID, +                KeychainContract.UserIds.RANK, +            };      static final String USER_IDS_SORT_ORDER = -            KeychainContract.UserIds.USER_ID + " COLLATE LOCALIZED ASC"; +            KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC";      static final String[] KEYS_PROJECTION =              new String[]{KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID, @@ -221,7 +240,7 @@ public class ViewKeyMainFragment extends Fragment implements                  // Now create and return a CursorLoader that will take care of                  // creating a Cursor for the data being displayed. -                return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null, +                return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null,                          USER_IDS_SORT_ORDER);              }              case LOADER_ID_KEYS: { @@ -306,7 +325,7 @@ public class ViewKeyMainFragment extends Fragment implements                      }                      String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); -                    mFingerprint.setText(colorizeFingerprint(fingerprint)); +                    mFingerprint.setText(OtherHelper.colorizeFingerprint(fingerprint));                  }                  mKeysAdapter.swapCursor(data); @@ -317,63 +336,6 @@ public class ViewKeyMainFragment extends Fragment implements          }      } -    private SpannableStringBuilder colorizeFingerprint(String fingerprint) { -        SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint); -        try { -            // for each 4 characters of the fingerprint + 1 space -            for (int i = 0; i < fingerprint.length(); i += 5) { -                int spanEnd = Math.min(i + 4, fingerprint.length()); -                String fourChars = fingerprint.substring(i, spanEnd); - -                int raw = Integer.parseInt(fourChars, 16); -                byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)}; -                int[] color = OtherHelper.getRgbForData(bytes); -                int r = color[0]; -                int g = color[1]; -                int b = color[2]; - -                // we cannot change black by multiplication, so adjust it to an almost-black grey, -                // which will then be brightened to the minimal brightness level -                if (r == 0 && g == 0 && b == 0) { -                    r = 1; -                    g = 1; -                    b = 1; -                } - -                // Convert rgb to brightness -                double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; - -                // If a color is too dark to be seen on black, -                // then brighten it up to a minimal brightness. -                if (brightness < 80) { -                    double factor = 80.0 / brightness; -                    r = Math.min(255, (int) (r * factor)); -                    g = Math.min(255, (int) (g * factor)); -                    b = Math.min(255, (int) (b * factor)); - -                    // If it is too light, then darken it to a respective maximal brightness. -                } else if (brightness > 180) { -                    double factor = 180.0 / brightness; -                    r = (int) (r * factor); -                    g = (int) (g * factor); -                    b = (int) (b * factor); -                } - -                // Create a foreground color with the 3 digest integers as RGB -                // and then converting that int to hex to use as a color -                sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)), -                        i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); -            } -        } catch (Exception e) { -            Log.e(Constants.TAG, "Colorization failed", e); -            // if anything goes wrong, then just display the fingerprint without colour, -            // instead of partially correct colour or wrong colours -            return new SpannableStringBuilder(fingerprint); -        } - -        return sb; -    } -      /**       * This is called when the last Cursor provided to onLoadFinished() above is about to be closed.       * We need to make sure we are no longer using it. diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java index 19f0d1eaf..05521b0c9 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListEntry.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.adapter;  import android.os.Parcel;  import android.os.Parcelable; +import android.util.SparseArray;  import org.spongycastle.openpgp.PGPKeyRing;  import org.spongycastle.openpgp.PGPPublicKey;  import org.spongycastle.openpgp.PGPSecretKeyRing; @@ -171,20 +172,31 @@ public class ImportKeysListEntry implements Serializable, Parcelable {                  .getFingerprint(), true);          this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId);          this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength(); -        int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); -        if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL -                || algorithm == PGPPublicKey.RSA_SIGN) { -            this.algorithm = "RSA"; -        } else if (algorithm == PGPPublicKey.DSA) { -            this.algorithm = "DSA"; -        } else if (algorithm == PGPPublicKey.ELGAMAL_ENCRYPT -                || algorithm == PGPPublicKey.ELGAMAL_GENERAL) { -            this.algorithm = "ElGamal"; -        } else if (algorithm == PGPPublicKey.EC || algorithm == PGPPublicKey.ECDSA) { -            this.algorithm = "ECC"; -        } else { -            // TODO: with resources -            this.algorithm = "unknown"; -        } +        final int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); +        this.algorithm = getAlgorithmFromId(algorithm); +    } + +    /** +     * Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a> +     */ +    private final static SparseArray<String> ALGORITHM_IDS = new SparseArray<String>() {{ +        put(-1, "unknown"); // TODO: with resources +        put(0, "unencrypted"); +        put(PGPPublicKey.RSA_GENERAL, "RSA"); +        put(PGPPublicKey.RSA_ENCRYPT, "RSA"); +        put(PGPPublicKey.RSA_SIGN, "RSA"); +        put(PGPPublicKey.ELGAMAL_ENCRYPT, "ElGamal"); +        put(PGPPublicKey.ELGAMAL_GENERAL, "ElGamal"); +        put(PGPPublicKey.DSA, "DSA"); +        put(PGPPublicKey.EC, "ECC"); +        put(PGPPublicKey.ECDSA, "ECC"); +        put(PGPPublicKey.ECDH, "ECC"); +    }}; + +    /** +     * Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a> +     */ +    public static String getAlgorithmFromId(int algorithmId) { +        return (ALGORITHM_IDS.get(algorithmId) != null ? ALGORITHM_IDS.get(algorithmId) : ALGORITHM_IDS.get(-1));      }  } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java index 3ba291c3d..61572b9ce 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java @@ -23,26 +23,48 @@ import android.support.v4.widget.CursorAdapter;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton;  import android.widget.TextView;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;  import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import java.util.ArrayList; +  public class ViewKeyUserIdsAdapter extends CursorAdapter {      private LayoutInflater mInflater; -    private int mIndexUserId; +    private int mIndexUserId, mIndexRank; -    public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) { +    final private ArrayList<Boolean> mCheckStates; + +    public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {          super(context, c, flags);          mInflater = LayoutInflater.from(context); +        mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null; +          initIndex(c);      } +    public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) { +        this(context, c, flags, false); +    }      @Override      public Cursor swapCursor(Cursor newCursor) {          initIndex(newCursor); +        if(mCheckStates != null) { +            mCheckStates.clear(); +            if(newCursor != null) { +                int count = newCursor.getCount(); +                mCheckStates.ensureCapacity(count); +                // initialize to true (use case knowledge: we usually want to sign all uids) +                for(int i = 0; i < count; i++) +                    mCheckStates.add(true); +            } +        }          return super.swapCursor(newCursor);      } @@ -56,20 +78,67 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {      private void initIndex(Cursor cursor) {          if (cursor != null) {              mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID); +            mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);          }      }      @Override      public void bindView(View view, Context context, Cursor cursor) { -        String userIdStr = cursor.getString(mIndexUserId); -        TextView userId = (TextView) view.findViewById(R.id.userId); -        userId.setText(userIdStr); +        TextView vRank = (TextView) view.findViewById(R.id.rank); +        TextView vUserId = (TextView) view.findViewById(R.id.userId); +        TextView vAddress = (TextView) view.findViewById(R.id.address); + +        vRank.setText(Integer.toString(cursor.getInt(mIndexRank))); + +        String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexUserId)); +        if (userId[0] != null) { +            vUserId.setText(userId[0]); +        } else { +            vUserId.setText(R.string.user_id_no_name); +        } +        vAddress.setText(userId[1]); + +        // don't care further if checkboxes aren't shown +        if(mCheckStates == null) +            return; + +        final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox); +        final int position = cursor.getPosition(); +        vCheckBox.setClickable(false); +        vCheckBox.setChecked(mCheckStates.get(position)); +        vCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { +            @Override +            public void onCheckedChanged(CompoundButton compoundButton, boolean b) { +                mCheckStates.set(position, b); +            } +        }); +        view.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View view) { +                vCheckBox.toggle(); +            } +        }); + +    } + +    public ArrayList<String> getSelectedUserIds() { +        ArrayList<String> result = new ArrayList<String>(); +        for(int i = 0; i < mCheckStates.size(); i++) { +            if(mCheckStates.get(i)) { +                mCursor.moveToPosition(i); +                result.add(mCursor.getString(mIndexUserId)); +            } +        } +        return result;      }      @Override      public View newView(Context context, Cursor cursor, ViewGroup parent) { -        return mInflater.inflate(R.layout.view_key_userids_item, null); +        View view = mInflater.inflate(R.layout.view_key_userids_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;      }  } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java index 42fb03a3e..0c07e9b06 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/HkpKeyServer.java @@ -43,13 +43,8 @@ import java.util.*;  import java.util.regex.Matcher;  import java.util.regex.Pattern; -/** - * TODO: - * rewrite to use machine readable output. - * <p/> - * see http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5 - * https://github.com/openpgp-keychain/openpgp-keychain/issues/259 - */ +import static org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry.getAlgorithmFromId; +  public class HkpKeyServer extends KeyServer {      private static class HttpError extends Exception {          private static final long serialVersionUID = 1718783705229428893L; @@ -74,16 +69,64 @@ public class HkpKeyServer extends KeyServer {      private String mHost;      private short mPort; -    // example: -    // pub 2048R/<a href="/pks/lookup?op=get&search=0x887DF4BE9F5C9090">9F5C9090</a> 2009-08-17 <a -    // href="/pks/lookup?op=vindex&search=0x887DF4BE9F5C9090">Jörg Runge -    // <joerg@joergrunge.de></a> +    /** +     * pub:%keyid%:%algo%:%keylen%:%creationdate%:%expirationdate%:%flags% +     * <ul> +     *     <li>%<b>keyid</b>% = this is either the fingerprint or the key ID of the key. Either the 16-digit or 8-digit +     *          key IDs are acceptable, but obviously the fingerprint is best.</li> +     *     <li>%<b>algo</b>% = the algorithm number, (i.e. 1==RSA, 17==DSA, etc). +     *          See <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a></li> +     *     <li>%<b>keylen</b>% = the key length (i.e. 1024, 2048, 4096, etc.)</li> +     *     <li>%<b>creationdate</b>% = creation date of the key in standard +     *          <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of seconds since +     *          1/1/1970 UTC time)</li> +     *     <li>%<b>expirationdate</b>% = expiration date of the key in standard +     *          <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of seconds since +     *          1/1/1970 UTC time)</li> +     *     <li>%<b>flags</b>% = letter codes to indicate details of the key, if any. Flags may be in any order. The +     *          meaning of "disabled" is implementation-specific. Note that individual flags may be unimplemented, so +     *          the absence of a given flag does not necessarily mean the absence of the detail. +     *          <ul> +     *              <li>r == revoked</li> +     *              <li>d == disabled</li> +     *              <li>e == expired</li> +     *          </ul> +     *     </li> +     * </ul> +     * +     * @see <a href="http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5.2">5.2. Machine Readable Indexes</a> +     * in Internet-Draft OpenPGP HTTP Keyserver Protocol Document +     */      public static final Pattern PUB_KEY_LINE = Pattern -            .compile( -                    "pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?:    +.+[\n\r]+)*)", +            .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]*))+", // one or more uid lines +                    Pattern.CASE_INSENSITIVE); + +    /** +     * uid:%escaped uid string%:%creationdate%:%expirationdate%:%flags% +     * <ul> +     *      <li>%<b>escaped uid string</b>% = the user ID string, with HTTP %-escaping for anything that isn't 7-bit +     *          safe as well as for the ":" character.  Any other characters may be escaped, as desired.</li> +     *      <li>%<b>creationdate</b>% = creation date of the key in standard +     *          <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of seconds since +     *          1/1/1970 UTC time)</li> +     *      <li>%<b>expirationdate</b>% = expiration date of the key in standard +     *          <a href="http://tools.ietf.org/html/rfc2440#section-9.1">RFC-2440</a> form (i.e. number of seconds since +     *          1/1/1970 UTC time)</li> +     *      <li>%<b>flags</b>% = letter codes to indicate details of the key, if any. Flags may be in any order. The +     *          meaning of "disabled" is implementation-specific. Note that individual flags may be unimplemented, so +     *          the absence of a given flag does not necessarily mean the absence of the detail. +     *          <ul> +     *              <li>r == revoked</li> +     *              <li>d == disabled</li> +     *              <li>e == expired</li> +     *          </ul> +     *      </li> +     * </ul> +     */ +    public static final Pattern UID_LINE = Pattern +            .compile("uid:(.*):([0-9]+):([0-9]*):([rde]*)",                      Pattern.CASE_INSENSITIVE); -    public static final Pattern USER_ID_LINE = Pattern.compile("^   +(.+)$", Pattern.MULTILINE -            | Pattern.CASE_INSENSITIVE);      private static final short PORT_DEFAULT = 11371; @@ -173,9 +216,9 @@ public class HkpKeyServer extends KeyServer {          } catch (UnsupportedEncodingException e) {              return null;          } -        String request = "/pks/lookup?op=index&search=" + encodedQuery; +        String request = "/pks/lookup?op=index&options=mr&search=" + encodedQuery; -        String data = null; +        String data;          try {              data = query(request);          } catch (HttpError e) { @@ -193,38 +236,41 @@ public class HkpKeyServer extends KeyServer {              throw new QueryException("querying server(s) for '" + mHost + "' failed");          } -        Matcher matcher = PUB_KEY_LINE.matcher(data); +        final Matcher matcher = PUB_KEY_LINE.matcher(data);          while (matcher.find()) { -            ImportKeysListEntry info = new ImportKeysListEntry(); -            info.bitStrength = Integer.parseInt(matcher.group(1)); -            info.algorithm = matcher.group(2); -            info.hexKeyId = "0x" + matcher.group(3); -            info.keyId = PgpKeyHelper.convertHexToKeyId(matcher.group(3)); -            String chunks[] = matcher.group(4).split("-"); - -            GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); -            tmpGreg.set(Integer.parseInt(chunks[0]), Integer.parseInt(chunks[1]), -                    Integer.parseInt(chunks[2])); +            final ImportKeysListEntry info = new ImportKeysListEntry(); +            info.bitStrength = Integer.parseInt(matcher.group(3)); +            final int algorithmId = Integer.decode(matcher.group(2)); +            info.algorithm = getAlgorithmFromId(algorithmId); + +            info.hexKeyId = "0x" + matcher.group(1); +            info.keyId = PgpKeyHelper.convertHexToKeyId(matcher.group(1)); + +            final long creationDate = Long.parseLong(matcher.group(4)); +            final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC")); +            tmpGreg.setTimeInMillis(creationDate * 1000);              info.date = tmpGreg.getTime(); + +            info.revoked = matcher.group(6).contains("r");              info.userIds = new ArrayList<String>(); -            if (matcher.group(5).startsWith("*** KEY")) { -                info.revoked = true; -            } else { -                String tmp = matcher.group(5).replaceAll("<.*?>", ""); -                tmp = Html.fromHtml(tmp).toString(); -                info.userIds.add(tmp); -            } -            if (matcher.group(6).length() > 0) { -                Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6)); -                while (matcher2.find()) { -                    String tmp = matcher2.group(1).replaceAll("<.*?>", ""); -                    tmp = Html.fromHtml(tmp).toString(); -                    info.userIds.add(tmp); + +            final String uidLines = matcher.group(7); +            final Matcher uidMatcher = UID_LINE.matcher(uidLines); +            while (uidMatcher.find()) { +                String tmp = uidMatcher.group(1).replaceAll("<.*?>", ""); +                tmp = Html.fromHtml(tmp).toString().trim(); +                if (tmp.contains("%")) { +                    try { +                        // converts Strings like "Universit%C3%A4t" to a proper encoding form "Universität". +                        tmp = (URLDecoder.decode(tmp, "UTF8")); +                    } catch (UnsupportedEncodingException ignored) { +                        // will never happen, because "UTF8" is supported +                    }                  } +                info.userIds.add(tmp);              }              results.add(info);          } -          return results;      } @@ -233,7 +279,7 @@ public class HkpKeyServer extends KeyServer {          HttpClient client = new DefaultHttpClient();          try {              HttpGet get = new HttpGet("http://" + mHost + ":" + mPort -                    + "/pks/lookup?op=get&search=" + PgpKeyHelper.convertKeyIdToHex(keyId)); +                    + "/pks/lookup?op=get&options=mr&search=" + PgpKeyHelper.convertKeyIdToHex(keyId));              HttpResponse response = client.execute(get);              if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java index caaa07524..40105df4f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/util/IterableIterator.java @@ -16,13 +16,21 @@  package org.sufficientlysecure.keychain.util; +import java.util.ArrayList;  import java.util.Iterator;  public class IterableIterator<T> implements Iterable<T> {      private Iterator<T> mIter; -    public IterableIterator(Iterator<T> iter) { +    public IterableIterator(Iterator<T> iter, boolean failsafe) {          mIter = iter; +        if(failsafe && mIter == null) { +            // is there a better way? +            mIter = new ArrayList<T>().iterator(); +        } +    } +    public IterableIterator(Iterator<T> iter) { +        this(iter, false);      }      public Iterator<T> iterator() {  | 
