diff options
Diffstat (limited to 'OpenKeychain/src/main')
45 files changed, 865 insertions, 1120 deletions
| diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 2283235e7..af09019e8 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -81,9 +81,14 @@              </intent-filter>          </activity>          <activity -            android:name=".ui.WizardActivity" +            android:name=".ui.FirstTimeActivity"              android:configChanges="orientation|screenSize|keyboardHidden|keyboard" -            android:label="@string/title_wizard" +            android:label="@string/app_name" +            android:windowSoftInputMode="stateHidden" /> +        <activity +            android:name=".ui.CreateKeyActivity" +            android:configChanges="orientation|screenSize|keyboardHidden|keyboard" +            android:label="@string/title_create_key"              android:windowSoftInputMode="stateHidden" />          <activity              android:name=".ui.EditKeyActivityOld" diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 86bc3fa5b..33ab52bca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -69,6 +69,7 @@ public final class Constants {          public static final String KEY_SERVERS = "keyServers";          public static final String KEY_SERVERS_DEFAULT_VERSION = "keyServersDefaultVersion";          public static final String CONCEAL_PGP_APPLICATION = "concealPgpApplication"; +        public static final String FIRST_TIME = "firstTime";      }      public static final class Defaults { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java index fd8267a59..e55c14a2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/Preferences.java @@ -139,6 +139,16 @@ public class Preferences {          editor.commit();      } +    public boolean getFirstTime() { +        return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true); +    } + +    public void setFirstTime(boolean value) { +        SharedPreferences.Editor editor = mSharedPreferences.edit(); +        editor.putBoolean(Constants.Pref.FIRST_TIME, value); +        editor.commit(); +    } +      public String[] getKeyServers() {          String rawData = mSharedPreferences.getString(Constants.Pref.KEY_SERVERS,                  Constants.Defaults.KEY_SERVERS); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java index 0a49cb629..30e93f957 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java @@ -250,7 +250,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {          mHashCode = key.hashCode(); -        mPrimaryUserId = key.getPrimaryUserId(); +        mPrimaryUserId = key.getPrimaryUserIdWithFallback();          mUserIds = key.getUnorderedUserIds();          // if there was no user id flagged as primary, use the first one diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 47b827677..129ffba3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -22,8 +22,10 @@ public abstract class KeyRing {      abstract public String getPrimaryUserId() throws PgpGeneralException; -    public String[] getSplitPrimaryUserId() throws PgpGeneralException { -        return splitUserId(getPrimaryUserId()); +    abstract public String getPrimaryUserIdWithFallback() throws PgpGeneralException; + +    public String[] getSplitPrimaryUserIdWithFallback() throws PgpGeneralException { +        return splitUserId(getPrimaryUserIdWithFallback());      }      abstract public boolean isRevoked() throws PgpGeneralException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index a5ccfbd3b..db9e2c6c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -258,7 +258,7 @@ public class PgpDecryptVerify {                      continue;                  }                  // get subkey which has been used for this encryption packet -                secretEncryptionKey = secretKeyRing.getSubKey(encData.getKeyID()); +                secretEncryptionKey = secretKeyRing.getSecretKey(encData.getKeyID());                  if (secretEncryptionKey == null) {                      // continue with the next packet in the while loop                      continue; @@ -393,7 +393,7 @@ public class PgpDecryptVerify {                      signingRing = mProviderHelper.getWrappedPublicKeyRing(                              KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)                      ); -                    signingKey = signingRing.getSubkey(sigKeyId); +                    signingKey = signingRing.getPublicKey(sigKeyId);                      signatureIndex = i;                  } catch (ProviderHelper.NotFoundException e) {                      Log.d(Constants.TAG, "key not found!"); @@ -409,7 +409,7 @@ public class PgpDecryptVerify {                  signatureResultBuilder.knownKey(true);                  signatureResultBuilder.keyId(signingRing.getMasterKeyId());                  try { -                    signatureResultBuilder.userId(signingRing.getPrimaryUserId()); +                    signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback());                  } catch(PgpGeneralException e) {                      Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId());                  } @@ -578,7 +578,7 @@ public class PgpDecryptVerify {                  signingRing = mProviderHelper.getWrappedPublicKeyRing(                          KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)                  ); -                signingKey = signingRing.getSubkey(sigKeyId); +                signingKey = signingRing.getPublicKey(sigKeyId);                  signatureIndex = i;              } catch (ProviderHelper.NotFoundException e) {                  Log.d(Constants.TAG, "key not found!"); @@ -596,7 +596,7 @@ public class PgpDecryptVerify {              signatureResultBuilder.knownKey(true);              signatureResultBuilder.keyId(signingRing.getMasterKeyId());              try { -                signatureResultBuilder.userId(signingRing.getPrimaryUserId()); +                signatureResultBuilder.userId(signingRing.getPrimaryUserIdWithFallback());              } catch(PgpGeneralException e) {                  Log.d(Constants.TAG, "No primary user id in key " + signingRing.getMasterKeyId());              } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 9a08290e4..bd8a9201e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -65,10 +65,8 @@ import java.security.NoSuchProviderException;  import java.security.SecureRandom;  import java.security.SignatureException;  import java.util.Arrays; -import java.util.Calendar;  import java.util.Date;  import java.util.Iterator; -import java.util.TimeZone;  /**   * This class is the single place where ALL operations that actually modify a PGP public or secret @@ -104,11 +102,12 @@ public class PgpKeyOperation {      }      /** Creates new secret key. */ -    private PGPKeyPair createKey(int algorithmChoice, int keySize) throws PgpGeneralMsgIdException { +    private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) {          try {              if (keySize < 512) { -                throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit); +                log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent); +                return null;              }              int algorithm; @@ -143,7 +142,8 @@ public class PgpKeyOperation {                  }                  default: { -                    throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice); +                    log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent); +                    return null;                  }              } @@ -157,7 +157,9 @@ public class PgpKeyOperation {          } catch(InvalidAlgorithmParameterException e) {              throw new RuntimeException(e);          } catch(PGPException e) { -            throw new PgpGeneralMsgIdException(R.string.msg_mf_error_pgp, e); +            Log.e(Constants.TAG, "internal pgp error", e); +            log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); +            return null;          }      } @@ -166,20 +168,36 @@ public class PgpKeyOperation {          try { -            log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent); +            log.add(LogLevel.START, LogType.MSG_CR, indent);              indent += 1;              updateProgress(R.string.progress_building_key, 0, 100); -            if (saveParcel.addSubKeys == null || saveParcel.addSubKeys.isEmpty()) { +            if (saveParcel.mAddSubKeys.isEmpty()) {                  log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent);                  return null;              } -            SubkeyAdd add = saveParcel.addSubKeys.remove(0); -            PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize); +            if (saveParcel.mAddUserIds.isEmpty()) { +                log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_USER_ID, indent); +                return null; +            } + +            SubkeyAdd add = saveParcel.mAddSubKeys.remove(0); +            if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) { +                log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CERTIFY, indent); +                return null; +            }              if (add.mAlgorithm == Constants.choice.algorithm.elgamal) { -                throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal); +                log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent); +                return null; +            } + +            PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent); + +            // return null if this failed (an error will already have been logged by createKey) +            if (keyPair == null) { +                return null;              }              // define hashing and signing algos @@ -195,17 +213,15 @@ public class PgpKeyOperation {              PGPSecretKeyRing sKR = new PGPSecretKeyRing(                      masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); -            return internal(sKR, masterSecretKey, saveParcel, "", log, indent); +            return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log, indent);          } catch (PGPException e) { +            log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);              Log.e(Constants.TAG, "pgp error encoding key", e);              return null;          } catch (IOException e) {              Log.e(Constants.TAG, "io error encoding key", e);              return null; -        } catch (PgpGeneralMsgIdException e) { -            Log.e(Constants.TAG, "pgp msg id error", e); -            return null;          }      } @@ -257,11 +273,16 @@ public class PgpKeyOperation {              return null;          } -        return internal(sKR, masterSecretKey, saveParcel, passphrase, log, indent); +        // read masterKeyFlags, and use the same as before. +        // since this is the master key, this contains at least CERTIFY_OTHER +        int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER; + +        return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log, indent);      }      private UncachedKeyRing internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey, +                                     int masterKeyFlags,                                       SaveKeyringParcel saveParcel, String passphrase,                                       OperationLog log, int indent) { @@ -289,7 +310,7 @@ public class PgpKeyOperation {              PGPPublicKey modifiedPublicKey = masterPublicKey;              // 2a. Add certificates for new user ids -            for (String userId : saveParcel.addUserIds) { +            for (String userId : saveParcel.mAddUserIds) {                  log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);                  // this operation supersedes all previous binding and revocation certificates, @@ -298,9 +319,10 @@ public class PgpKeyOperation {                  Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);                  if (it != null) {                      for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) { -                        // if it's not a self cert, never mind                          if (cert.getKeyID() != masterPublicKey.getKeyID()) { -                            continue; +                            // foreign certificate?! error error error +                            log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); +                            return null;                          }                          if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION                                  || cert.getSignatureType() == PGPSignature.NO_CERTIFICATION @@ -314,27 +336,31 @@ public class PgpKeyOperation {                  }                  // if it's supposed to be primary, we can do that here as well -                boolean isPrimary = saveParcel.changePrimaryUserId != null -                        && userId.equals(saveParcel.changePrimaryUserId); +                boolean isPrimary = saveParcel.mChangePrimaryUserId != null +                        && userId.equals(saveParcel.mChangePrimaryUserId);                  // generate and add new certificate                  PGPSignature cert = generateUserIdSignature(masterPrivateKey, -                        masterPublicKey, userId, isPrimary); -                modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); +                        masterPublicKey, userId, isPrimary, masterKeyFlags); +                modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);              }              // 2b. Add revocations for revoked user ids -            for (String userId : saveParcel.revokeUserIds) { +            for (String userId : saveParcel.mRevokeUserIds) {                  log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent); -                // a duplicate revocatin will be removed during canonicalization, so no need to +                // a duplicate revocation will be removed during canonicalization, so no need to                  // take care of that here.                  PGPSignature cert = generateRevocationSignature(masterPrivateKey,                          masterPublicKey, userId); -                modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); +                modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);              }              // 3. If primary user id changed, generate new certificates for both old and new -            if (saveParcel.changePrimaryUserId != null) { +            if (saveParcel.mChangePrimaryUserId != null) { + +                // keep track if we actually changed one +                boolean ok = false;                  log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent); +                indent += 1;                  // we work on the modifiedPublicKey here, to respect new or newly revoked uids                  // noinspection unchecked @@ -343,10 +369,11 @@ public class PgpKeyOperation {                      PGPSignature currentCert = null;                      // noinspection unchecked                      for (PGPSignature cert : new IterableIterator<PGPSignature>( -                            masterPublicKey.getSignaturesForID(userId))) { -                        // if it's not a self cert, never mind +                            modifiedPublicKey.getSignaturesForID(userId))) {                          if (cert.getKeyID() != masterPublicKey.getKeyID()) { -                            continue; +                            // foreign certificate?! error error error +                            log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent); +                            return null;                          }                          // we know from canonicalization that if there is any revocation here, it                          // is valid and not superseded by a newer certification. @@ -373,7 +400,7 @@ public class PgpKeyOperation {                      // we definitely should not update certifications of revoked keys, so just leave it.                      if (isRevoked) {                          // revoked user ids cannot be primary! -                        if (userId.equals(saveParcel.changePrimaryUserId)) { +                        if (userId.equals(saveParcel.mChangePrimaryUserId)) {                              log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);                              return null;                          } @@ -383,14 +410,16 @@ public class PgpKeyOperation {                      // if this is~ the/a primary user id                      if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {                          // if it's the one we want, just leave it as is -                        if (userId.equals(saveParcel.changePrimaryUserId)) { +                        if (userId.equals(saveParcel.mChangePrimaryUserId)) { +                            ok = true;                              continue;                          }                          // otherwise, generate new non-primary certification +                        log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);                          modifiedPublicKey = PGPPublicKey.removeCertification(                                  modifiedPublicKey, userId, currentCert);                          PGPSignature newCert = generateUserIdSignature( -                                masterPrivateKey, masterPublicKey, userId, false); +                                masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags);                          modifiedPublicKey = PGPPublicKey.addCertification(                                  modifiedPublicKey, userId, newCert);                          continue; @@ -399,20 +428,28 @@ public class PgpKeyOperation {                      // if we are here, this is not currently a primary user id                      // if it should be -                    if (userId.equals(saveParcel.changePrimaryUserId)) { +                    if (userId.equals(saveParcel.mChangePrimaryUserId)) {                          // add shiny new primary user id certificate +                        log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent);                          modifiedPublicKey = PGPPublicKey.removeCertification(                                  modifiedPublicKey, userId, currentCert);                          PGPSignature newCert = generateUserIdSignature( -                                masterPrivateKey, masterPublicKey, userId, true); +                                masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags);                          modifiedPublicKey = PGPPublicKey.addCertification(                                  modifiedPublicKey, userId, newCert); +                        ok = true;                      }                      // user id is not primary and is not supposed to be - nothing to do here.                  } +                indent -= 1; + +                if (!ok) { +                    log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent); +                    return null; +                }              }              // Update the secret key ring @@ -423,9 +460,16 @@ public class PgpKeyOperation {              }              // 4a. For each subkey change, generate new subkey binding certificate -            for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) { +            for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {                  log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,                          indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); + +                // TODO allow changes in master key? this implies generating new user id certs... +                if (change.mKeyId == masterPublicKey.getKeyID()) { +                    Log.e(Constants.TAG, "changing the master key not supported"); +                    return null; +                } +                  PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);                  if (sKey == null) {                      log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, @@ -434,22 +478,45 @@ public class PgpKeyOperation {                  }                  PGPPublicKey pKey = sKey.getPublicKey(); -                if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) { +                // expiry must not be in the past +                if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) {                      log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,                              indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));                      return null;                  } -                // generate and add new signature. we can be sloppy here and just leave the old one, -                // it will be removed during canonicalization +                // keep old flags, or replace with new ones +                int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags; +                long expiry; +                if (change.mExpiry == null) { +                    long valid = pKey.getValidSeconds(); +                    expiry = valid == 0 +                            ? 0 +                            : pKey.getCreationTime().getTime() / 1000 + pKey.getValidSeconds(); +                } else { +                    expiry = change.mExpiry; +                } + +                // drop all old signatures, they will be superseded by the new one +                //noinspection unchecked +                for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) { +                    // special case: if there is a revocation, don't use expiry from before +                    if (change.mExpiry == null +                            && sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) { +                        expiry = 0; +                    } +                    pKey = PGPPublicKey.removeCertification(pKey, sig); +                } + +                // generate and add new signature                  PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, -                        sKey, pKey, change.mFlags, change.mExpiry, passphrase); +                        sKey, pKey, flags, expiry, passphrase);                  pKey = PGPPublicKey.addCertification(pKey, sig);                  sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));              }              // 4b. For each subkey revocation, generate new subkey revocation certificate -            for (long revocation : saveParcel.revokeSubKeys) { +            for (long revocation : saveParcel.mRevokeSubKeys) {                  log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,                          indent, PgpKeyHelper.convertKeyIdToHex(revocation));                  PGPSecretKey sKey = sKR.getSecretKey(revocation); @@ -468,52 +535,51 @@ public class PgpKeyOperation {              }              // 5. Generate and add new subkeys -            for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) { -                try { - -                    if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) { -                        log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1); -                        return null; -                    } - -                    log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent); +            for (SaveKeyringParcel.SubkeyAdd add : saveParcel.mAddSubKeys) { -                    // generate a new secret key (privkey only for now) -                    PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize); - -                    // add subkey binding signature (making this a sub rather than master key) -                    PGPPublicKey pKey = keyPair.getPublicKey(); -                    PGPSignature cert = generateSubkeyBindingSignature( -                            masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, -                            add.mFlags, add.mExpiry); -                    pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); +                if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) { +                    log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1); +                    return null; +                } -                    PGPSecretKey sKey; { -                        // define hashing and signing algos -                        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() -                                .build().get(HashAlgorithmTags.SHA1); +                log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent); -                        // Build key encrypter and decrypter based on passphrase -                        PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( -                                PGPEncryptedData.CAST5, sha1Calc) -                                .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); +                // generate a new secret key (privkey only for now) +                PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent); +                if(keyPair == null) { +                    return null; +                } -                        sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, -                                sha1Calc, false, keyEncryptor); -                    } +                // add subkey binding signature (making this a sub rather than master key) +                PGPPublicKey pKey = keyPair.getPublicKey(); +                PGPSignature cert = generateSubkeyBindingSignature( +                        masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, +                        add.mFlags, add.mExpiry == null ? 0 : add.mExpiry); +                pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); + +                PGPSecretKey sKey; { +                    // define hashing and signing algos +                    PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder() +                            .build().get(HashAlgorithmTags.SHA1); + +                    // Build key encrypter and decrypter based on passphrase +                    PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( +                            PGPEncryptedData.CAST5, sha1Calc) +                            .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray()); + +                    sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, +                            sha1Calc, false, keyEncryptor); +                } -                    log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID, -                            indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID())); +                log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID, +                        indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID())); -                    sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); +                sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey); -                } catch (PgpGeneralMsgIdException e) { -                    return null; -                }              }              // 6. If requested, change passphrase -            if (saveParcel.newPassphrase != null) { +            if (saveParcel.mNewPassphrase != null) {                  log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);                  PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()                          .get(HashAlgorithmTags.SHA1); @@ -523,7 +589,7 @@ public class PgpKeyOperation {                  PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(                          PGPEncryptedData.CAST5, sha1Calc)                          .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( -                                saveParcel.newPassphrase.toCharArray()); +                                saveParcel.mNewPassphrase.toCharArray());                  sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);              } @@ -546,7 +612,7 @@ public class PgpKeyOperation {      }      private static PGPSignature generateUserIdSignature( -            PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary) +            PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags)              throws IOException, PGPException, SignatureException {          PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(                  pKey.getAlgorithm(), PGPUtil.SHA1) @@ -558,6 +624,7 @@ public class PgpKeyOperation {          subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);          subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);          subHashedPacketsGen.setPrimaryUserID(false, primary); +        subHashedPacketsGen.setKeyFlags(false, flags);          sGen.setHashedSubpackets(subHashedPacketsGen.generate());          sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);          return sGen.generateCertification(userId, pKey); @@ -599,7 +666,7 @@ public class PgpKeyOperation {      private static PGPSignature generateSubkeyBindingSignature(              PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, -            PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase) +            PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, String passphrase)              throws IOException, PGPException, SignatureException {          PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()                  .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( @@ -611,7 +678,7 @@ public class PgpKeyOperation {      private static PGPSignature generateSubkeyBindingSignature(              PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, -            PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry) +            PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)              throws IOException, PGPException, SignatureException {          // date for signing @@ -640,17 +707,9 @@ public class PgpKeyOperation {              hashedPacketsGen.setKeyFlags(false, flags);          } -        if (expiry != null) { -            Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")); -            creationDate.setTime(pKey.getCreationTime()); - -            // (Just making sure there's no programming error here, this MUST have been checked above!) -            if (new Date(expiry).before(todayDate)) { -                throw new RuntimeException("Bad subkey creation date, this is a bug!"); -            } -            hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis()); -        } else { -            hashedPacketsGen.setKeyExpirationTime(false, 0); +        if (expiry > 0) { +            long creationTime = pKey.getCreationTime().getTime() / 1000; +            hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime);          }          PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( @@ -665,4 +724,22 @@ public class PgpKeyOperation {      } +    /** Returns all flags valid for this key. +     * +     * This method does not do any validity checks on the signature, so it should not be used on +     * a non-canonicalized key! +     * +     */ +    private static int readKeyFlags(PGPPublicKey key) { +        int flags = 0; +        //noinspection unchecked +        for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) { +            if (!sig.hasSubpackets()) { +                continue; +            } +            flags |= sig.getHashedSubPackets().getKeyFlags(); +        } +        return flags; +    } +  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 441e2762a..9ddfd3405 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -81,6 +81,10 @@ public class UncachedKeyRing {          return new UncachedPublicKey(mRing.getPublicKey());      } +    public UncachedPublicKey getPublicKey(long keyId) { +        return new UncachedPublicKey(mRing.getPublicKey(keyId)); +    } +      public Iterator<UncachedPublicKey> getPublicKeys() {          final Iterator<PGPPublicKey> it = mRing.getPublicKeys();          return new Iterator<UncachedPublicKey>() { @@ -558,7 +562,7 @@ public class UncachedKeyRing {                      // make sure the certificate checks out                      try {                          cert.init(masterKey); -                        if (!cert.verifySignature(key)) { +                        if (!cert.verifySignature(masterKey, key)) {                              log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, indent);                              badCerts += 1;                              continue; @@ -744,8 +748,12 @@ public class UncachedKeyRing {              } -            log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, -                    indent, Integer.toString(newCerts)); +            if (newCerts > 0) { +                log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW, indent, +                        Integer.toString(newCerts)); +            } else { +                log.add(LogLevel.DEBUG, LogType.MSG_MG_UNCHANGED, indent); +            }              return new UncachedKeyRing(result); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 33db7771b..ef5aa8e86 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -9,6 +9,7 @@ import org.spongycastle.openpgp.PGPSignatureSubpacketVector;  import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.util.IterableIterator; +import org.sufficientlysecure.keychain.util.Log;  import java.security.SignatureException;  import java.util.ArrayList; @@ -44,14 +45,19 @@ public class UncachedPublicKey {      }      public Date getExpiryTime() { -        Date creationDate = getCreationTime(); -        if (mPublicKey.getValidDays() == 0) { +        long seconds = mPublicKey.getValidSeconds(); +        if (seconds > Integer.MAX_VALUE) { +            Log.e(Constants.TAG, "error, expiry time too large"); +            return null; +        } +        if (seconds == 0) {              // no expiry              return null;          } +        Date creationDate = getCreationTime();          Calendar calendar = GregorianCalendar.getInstance();          calendar.setTime(creationDate); -        calendar.add(Calendar.DATE, mPublicKey.getValidDays()); +        calendar.add(Calendar.SECOND, (int) seconds);          return calendar.getTime();      } @@ -77,26 +83,76 @@ public class UncachedPublicKey {          return mPublicKey.getBitStrength();      } +    /** Returns the primary user id, as indicated by the public key's self certificates. +     * +     * This is an expensive operation, since potentially a lot of certificates (and revocations) +     * have to be checked, and even then the result is NOT guaranteed to be constant through a +     * canonicalization operation. +     * +     * Returns null if there is no primary user id (as indicated by certificates) +     * +     */      public String getPrimaryUserId() { +        String found = null; +        PGPSignature foundSig = null;          for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) { +            PGPSignature revocation = null; +              for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignaturesForID(userId))) { -                if (sig.getHashedSubPackets() != null -                        && sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) { -                    try { +                try { + +                    // if this is a revocation, this is not the user id +                    if (sig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) { +                        // make sure it's actually valid +                        sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider( +                                Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey); +                        if (!sig.verifyCertification(userId, mPublicKey)) { +                            continue; +                        } +                        if (found != null && found.equals(userId)) { +                            found = null; +                        } +                        revocation = sig; +                        // this revocation may still be overridden by a newer cert +                        continue; +                    } + +                    if (sig.getHashedSubPackets() != null && sig.getHashedSubPackets().isPrimaryUserID()) { +                        if (foundSig != null && sig.getCreationTime().before(foundSig.getCreationTime())) { +                            continue; +                        } +                        // ignore if there is a newer revocation for this user id +                        if (revocation != null && sig.getCreationTime().before(revocation.getCreationTime())) { +                            continue; +                        }                          // make sure it's actually valid                          sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(                                  Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);                          if (sig.verifyCertification(userId, mPublicKey)) { -                            return userId; +                            found = userId; +                            foundSig = sig; +                            // this one can't be relevant anymore at this point +                            revocation = null;                          } -                    } catch (Exception e) { -                        // nothing bad happens, the key is just not considered the primary key id                      } -                } +                } catch (Exception e) { +                    // nothing bad happens, the key is just not considered the primary key id +                }              }          } -        return null; +        return found; +    } + +    /** +     * Returns primary user id if existing. If not, return first encountered user id. +     */ +    public String getPrimaryUserIdWithFallback()  { +        String userId = getPrimaryUserId(); +        if (userId == null) { +            userId = (String) mPublicKey.getUserIDs().next(); +        } +        return userId;      }      public ArrayList<String> getUnorderedUserIds() { @@ -186,6 +242,21 @@ public class UncachedPublicKey {          return mPublicKey;      } +    public Iterator<WrappedSignature> getSignatures() { +        final Iterator<PGPSignature> it = mPublicKey.getSignatures(); +        return new Iterator<WrappedSignature>() { +            public void remove() { +                it.remove(); +            } +            public WrappedSignature next() { +                return new WrappedSignature(it.next()); +            } +            public boolean hasNext() { +                return it.hasNext(); +            } +        }; +    } +      public Iterator<WrappedSignature> getSignaturesForId(String userId) {          final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId);          return new Iterator<WrappedSignature>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java index 6f3068261..a054255dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java @@ -39,8 +39,12 @@ public abstract class WrappedKeyRing extends KeyRing {      }      public String getPrimaryUserId() throws PgpGeneralException { -        return (String) getRing().getPublicKey().getUserIDs().next(); -    }; +        return getPublicKey().getPrimaryUserId(); +    } + +    public String getPrimaryUserIdWithFallback() throws PgpGeneralException { +        return getPublicKey().getPrimaryUserIdWithFallback(); +    }      public boolean isRevoked() throws PgpGeneralException {          // Is the master key revoked? @@ -101,8 +105,16 @@ public abstract class WrappedKeyRing extends KeyRing {      abstract public IterableIterator<WrappedPublicKey> publicKeyIterator(); -    public UncachedKeyRing getUncached() { -        return new UncachedKeyRing(getRing()); +    public WrappedPublicKey getPublicKey() { +        return new WrappedPublicKey(this, getRing().getPublicKey()); +    } + +    public WrappedPublicKey getPublicKey(long id) { +        return new WrappedPublicKey(this, getRing().getPublicKey(id)); +    } + +    public byte[] getEncoded() throws IOException { +        return getRing().getEncoded();      }  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java index b2abf15a4..57d84072a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java @@ -44,14 +44,6 @@ public class WrappedPublicKeyRing extends WrappedKeyRing {          getRing().encode(stream);      } -    public WrappedPublicKey getSubkey() { -        return new WrappedPublicKey(this, getRing().getPublicKey()); -    } - -    public WrappedPublicKey getSubkey(long id) { -        return new WrappedPublicKey(this, getRing().getPublicKey(id)); -    } -      /** Getter that returns the subkey that should be used for signing. */      WrappedPublicKey getEncryptionSubKey() throws PgpGeneralException {          PGPPublicKey key = getRing().getPublicKey(getEncryptId()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java index ef8044a9b..98ad2b706 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKey.java @@ -97,7 +97,7 @@ public class WrappedSecretKey extends WrappedPublicKey {              signatureGenerator.init(signatureType, mPrivateKey);              PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); -            spGen.setSignerUserID(false, mRing.getPrimaryUserId()); +            spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());              signatureGenerator.setHashedSubpackets(spGen.generate());              return signatureGenerator;          } catch(PGPException e) { @@ -175,7 +175,7 @@ public class WrappedSecretKey extends WrappedPublicKey {          }          // get the master subkey (which we certify for) -        PGPPublicKey publicKey = publicKeyRing.getSubkey().getPublicKey(); +        PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();          // fetch public key ring, add the certification and return it          for (String userId : new IterableIterator<String>(userIds.iterator())) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java index c737b7c46..5cb24cf88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java @@ -41,11 +41,11 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {          return mRing;      } -    public WrappedSecretKey getSubKey() { +    public WrappedSecretKey getSecretKey() {          return new WrappedSecretKey(this, mRing.getSecretKey());      } -    public WrappedSecretKey getSubKey(long id) { +    public WrappedSecretKey getSecretKey(long id) {          return new WrappedSecretKey(this, mRing.getSecretKey(id));      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index 196ac1dee..df19930c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -15,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log;  import java.io.IOException;  import java.security.SignatureException; +import java.util.ArrayList;  import java.util.Date;  /** OpenKeychain wrapper around PGPSignature objects. @@ -55,12 +56,37 @@ public class WrappedSignature {          return mSig.getCreationTime();      } +    public ArrayList<WrappedSignature> getEmbeddedSignatures() { +        ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>(); +        if (!mSig.hasSubpackets()) { +            return sigs; +        } +        try { +            PGPSignatureList list; +            list = mSig.getHashedSubPackets().getEmbeddedSignatures(); +            for(int i = 0; i < list.size(); i++) { +                sigs.add(new WrappedSignature(list.get(i))); +            } +            list = mSig.getUnhashedSubPackets().getEmbeddedSignatures(); +            for(int i = 0; i < list.size(); i++) { +                sigs.add(new WrappedSignature(list.get(i))); +            } +        } catch (PGPException e) { +            // no matter +            Log.e(Constants.TAG, "exception reading embedded signatures", e); +        } catch (IOException e) { +            // no matter +            Log.e(Constants.TAG, "exception reading embedded signatures", e); +        } +        return sigs; +    } +      public byte[] getEncoded() throws IOException {          return mSig.getEncoded();      }      public boolean isRevocation() { -        return mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.REVOCATION_REASON); +        return mSig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION;      }      public boolean isPrimaryUserId() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 48d40430a..34de0024d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -70,6 +70,10 @@ public class CachedPublicKeyRing extends KeyRing {          }      } +    public String getPrimaryUserIdWithFallback() throws PgpGeneralException { +        return getPrimaryUserId(); +    } +      public boolean isRevoked() throws PgpGeneralException {          try {              Object data = mProviderHelper.getGenericData(mUri, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index b5609a327..2d524f5b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -199,7 +199,7 @@ public class ProviderHelper {                  byte[] blob = cursor.getBlob(3);                  if (blob != null) {                      result.put(masterKeyId, -                            new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey()); +                            new WrappedPublicKeyRing(blob, hasAnySecret, verified).getPublicKey());                  }              } while (cursor.moveToNext()); @@ -450,8 +450,7 @@ public class ProviderHelper {                              if (cert.verifySignature(masterKey, userId)) {                                  item.trustedCerts.add(cert);                                  log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD, -                                        PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()), -                                        trustedKey.getPrimaryUserId() +                                        PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId())                                  );                              } else {                                  log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD); @@ -670,7 +669,7 @@ public class ProviderHelper {              // If there is an old keyring, merge it              try { -                UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached(); +                UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();                  // Merge data from new public ring into the old one                  publicRing = oldPublicRing.merge(publicRing, mLog, mIndent); @@ -706,7 +705,7 @@ public class ProviderHelper {              // If there is a secret key, merge new data (if any) and save the key for later              UncachedKeyRing secretRing;              try { -                secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached(); +                secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();                  // Merge data from new public ring into secret one                  secretRing = secretRing.merge(publicRing, mLog, mIndent); @@ -754,10 +753,10 @@ public class ProviderHelper {              // If there is an old secret key, merge it.              try { -                UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached(); +                UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncachedKeyRing();                  // Merge data from new secret ring into old one -                secretRing = oldSecretRing.merge(secretRing, mLog, mIndent); +                secretRing = secretRing.merge(oldSecretRing, mLog, mIndent);                  // If this is null, there is an error in the log so we can just return                  if (secretRing == null) { @@ -791,9 +790,9 @@ public class ProviderHelper {              // Merge new data into public keyring as well, if there is any              UncachedKeyRing publicRing;              try { -                UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached(); +                UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing(); -                // Merge data from new public ring into secret one +                // Merge data from new secret ring into public one                  publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);                  if (publicRing == null) {                      return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 10faa8786..9a4cef2f1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;  import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;  import org.sufficientlysecure.keychain.pgp.Progressable;  import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.WrappedPublicKey;  import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;  import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;  import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing; @@ -350,9 +349,9 @@ public class KeychainIntentService extends IntentService                      providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));                      // cache new passphrase -                    if (saveParcel.newPassphrase != null) { +                    if (saveParcel.mNewPassphrase != null) {                          PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(), -                                saveParcel.newPassphrase, ring.getPublicKey().getPrimaryUserId()); +                                saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback());                      }                  } catch (ProviderHelper.NotFoundException e) {                      sendErrorToHandler(e); @@ -546,7 +545,7 @@ public class KeychainIntentService extends IntentService                  ProviderHelper providerHelper = new ProviderHelper(this);                  WrappedPublicKeyRing publicRing = providerHelper.getWrappedPublicKeyRing(pubKeyId);                  WrappedSecretKeyRing secretKeyRing = providerHelper.getWrappedSecretKeyRing(masterKeyId); -                WrappedSecretKey certificationKey = secretKeyRing.getSubKey(); +                WrappedSecretKey certificationKey = secretKeyRing.getSecretKey();                  if(!certificationKey.unlock(signaturePassphrase)) {                      throw new PgpGeneralException("Error extracting key (bad passphrase?)");                  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index f868d931c..705d6afaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -9,6 +9,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator;  import org.sufficientlysecure.keychain.util.Log;  import java.util.ArrayList; +import java.util.Arrays;  import java.util.Iterator;  import java.util.List; @@ -70,9 +71,6 @@ public class OperationResultParcel implements Parcelable {              mParameters = parameters;              mIndent = indent;          } -        public LogEntryParcel(LogLevel level, LogType type, Object... parameters) { -            this(level, type, 0, parameters); -        }          public LogEntryParcel(Parcel source) {              mLevel = LogLevel.values()[source.readInt()]; @@ -104,6 +102,15 @@ public class OperationResultParcel implements Parcelable {              }          }; +        @Override +        public String toString() { +            return "LogEntryParcel{" + +                    "mLevel=" + mLevel + +                    ", mType=" + mType + +                    ", mParameters=" + Arrays.toString(mParameters) + +                    ", mIndent=" + mIndent + +                    '}'; +        }      }      /** This is an enum of all possible log events. @@ -235,9 +242,17 @@ public class OperationResultParcel implements Parcelable {          MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),          MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),          MSG_MG_FOUND_NEW (R.string.msg_mg_found_new), +        MSG_MG_UNCHANGED (R.string.msg_mg_unchanged),          // secret key create -        MSG_CR_ERROR_NO_MASTER (R.string.msg_mr), +        MSG_CR (R.string.msg_cr), +        MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master), +        MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id), +        MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify), +        MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512), +        MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo), +        MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp), +        MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal),          // secret key modify          MSG_MF (R.string.msg_mr), @@ -245,10 +260,13 @@ public class OperationResultParcel implements Parcelable {          MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),          MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),          MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity), +        MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary),          MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),          MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),          MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),          MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase), +        MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old), +        MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),          MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),          MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),          MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id), @@ -314,6 +332,7 @@ public class OperationResultParcel implements Parcelable {          }          public void add(LogLevel level, LogType type, int indent) { +            Log.d(Constants.TAG, type.toString());              mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));          } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 72c1a8f67..13d9b497f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -191,7 +191,7 @@ public class PassphraseCacheService extends Service {                  Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");                  try { -                    addCachedPassphrase(this, keyId, "", key.getPrimaryUserId()); +                    addCachedPassphrase(this, keyId, "", key.getPrimaryUserIdWithFallback());                  } catch (PgpGeneralException e) {                      Log.d(Constants.TAG, "PgpGeneralException occured");                  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index a56095767..5e90b396e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -27,23 +27,19 @@ public class SaveKeyringParcel implements Parcelable {      // the key fingerprint, for safety. MUST be null for a new key.      public byte[] mFingerprint; -    public String newPassphrase; +    public String mNewPassphrase; -    public ArrayList<String> addUserIds; -    public ArrayList<SubkeyAdd> addSubKeys; +    public ArrayList<String> mAddUserIds; +    public ArrayList<SubkeyAdd> mAddSubKeys; -    public ArrayList<SubkeyChange> changeSubKeys; -    public String changePrimaryUserId; +    public ArrayList<SubkeyChange> mChangeSubKeys; +    public String mChangePrimaryUserId; -    public ArrayList<String> revokeUserIds; -    public ArrayList<Long> revokeSubKeys; +    public ArrayList<String> mRevokeUserIds; +    public ArrayList<Long> mRevokeSubKeys;      public SaveKeyringParcel() { -        addUserIds = new ArrayList<String>(); -        addSubKeys = new ArrayList<SubkeyAdd>(); -        changeSubKeys = new ArrayList<SubkeyChange>(); -        revokeUserIds = new ArrayList<String>(); -        revokeSubKeys = new ArrayList<Long>(); +        reset();      }      public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) { @@ -52,6 +48,16 @@ public class SaveKeyringParcel implements Parcelable {          mFingerprint = fingerprint;      } +    public void reset() { +        mNewPassphrase = null; +        mAddUserIds = new ArrayList<String>(); +        mAddSubKeys = new ArrayList<SubkeyAdd>(); +        mChangePrimaryUserId = null; +        mChangeSubKeys = new ArrayList<SubkeyChange>(); +        mRevokeUserIds = new ArrayList<String>(); +        mRevokeSubKeys = new ArrayList<Long>(); +    } +      // performance gain for using Parcelable here would probably be negligible,      // use Serializable instead.      public static class SubkeyAdd implements Serializable { @@ -70,6 +76,7 @@ public class SaveKeyringParcel implements Parcelable {      public static class SubkeyChange implements Serializable {          public long mKeyId;          public Integer mFlags; +        // this is a long unix timestamp, in seconds (NOT MILLISECONDS!)          public Long mExpiry;          public SubkeyChange(long keyId, Integer flags, Long expiry) {              mKeyId = keyId; @@ -82,16 +89,16 @@ public class SaveKeyringParcel implements Parcelable {          mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;          mFingerprint = source.createByteArray(); -        newPassphrase = source.readString(); +        mNewPassphrase = source.readString(); -        addUserIds = source.createStringArrayList(); -        addSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable(); +        mAddUserIds = source.createStringArrayList(); +        mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable(); -        changeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable(); -        changePrimaryUserId = source.readString(); +        mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable(); +        mChangePrimaryUserId = source.readString(); -        revokeUserIds = source.createStringArrayList(); -        revokeSubKeys = (ArrayList<Long>) source.readSerializable(); +        mRevokeUserIds = source.createStringArrayList(); +        mRevokeSubKeys = (ArrayList<Long>) source.readSerializable();      }      @Override @@ -102,16 +109,16 @@ public class SaveKeyringParcel implements Parcelable {          }          destination.writeByteArray(mFingerprint); -        destination.writeString(newPassphrase); +        destination.writeString(mNewPassphrase); -        destination.writeStringList(addUserIds); -        destination.writeSerializable(addSubKeys); +        destination.writeStringList(mAddUserIds); +        destination.writeSerializable(mAddSubKeys); -        destination.writeSerializable(changeSubKeys); -        destination.writeString(changePrimaryUserId); +        destination.writeSerializable(mChangeSubKeys); +        destination.writeString(mChangePrimaryUserId); -        destination.writeStringList(revokeUserIds); -        destination.writeSerializable(revokeSubKeys); +        destination.writeStringList(mRevokeUserIds); +        destination.writeSerializable(mRevokeSubKeys);      }      public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java deleted file mode 100644 index a565f0707..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/KeyringTestingHelper.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.sufficientlysecure.keychain.testsupport; - -import android.content.Context; - -import org.sufficientlysecure.keychain.pgp.NullProgressable; -import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.OperationResults; - -import java.util.Collection; - -/** - * Helper for tests of the Keyring import in ProviderHelper. - */ -public class KeyringTestingHelper { - -    private final Context context; - -    public KeyringTestingHelper(Context robolectricContext) { -        this.context = robolectricContext; -    } - -    public boolean addKeyring(Collection<String> blobFiles) throws Exception { - -        ProviderHelper providerHelper = new ProviderHelper(context); - -        byte[] data = TestDataUtil.readAllFully(blobFiles); -        UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data); -        long masterKeyId = ring.getMasterKeyId(); - -        // Should throw an exception; key is not yet saved -        retrieveKeyAndExpectNotFound(providerHelper, masterKeyId); - -        OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable()); - -        boolean saveSuccess = saveKeyringResult.success(); - -        // Now re-retrieve the saved key. Should not throw an exception. -        providerHelper.getWrappedPublicKeyRing(masterKeyId); - -        // A different ID should still fail -        retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1); - -        return saveSuccess; -    } - - -    private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) { -        try { -            providerHelper.getWrappedPublicKeyRing(masterKeyId); -            throw new AssertionError("Was expecting the previous call to fail!"); -        } catch (ProviderHelper.NotFoundException expectedException) { -            // good -        } -    } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java deleted file mode 100644 index 1ab5878cc..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/PgpVerifyTestingHelper.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.sufficientlysecure.keychain.testsupport; - -import android.content.Context; - -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.util.InputData; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * For functional tests of PgpDecryptVerify - */ -public class PgpVerifyTestingHelper { - -    private final Context context; - -    public PgpVerifyTestingHelper(Context robolectricContext) { -        this.context = robolectricContext; -    } - -    public int doTestFile(String testFileName) throws Exception { -        ProviderHelper providerHelper = new ProviderHelperStub(context); - -        PgpDecryptVerify.PassphraseCache passphraseCache = new PgpDecryptVerify.PassphraseCache() { -            public String getCachedPassphrase(long masterKeyId) { -                return "I am a passphrase"; -            } -        }; - -        byte[] sampleInputBytes = TestDataUtil.readFully(getClass().getResourceAsStream(testFileName)); - -        InputStream sampleInput = new ByteArrayInputStream(sampleInputBytes); - -        InputData data = new InputData(sampleInput, sampleInputBytes.length); -        OutputStream outStream = new ByteArrayOutputStream(); - -        PgpDecryptVerify verify = new PgpDecryptVerify.Builder(providerHelper, passphraseCache, data, outStream).build(); -        PgpDecryptVerifyResult result = verify.execute(); - -        return result.getSignatureResult().getStatus(); -    } - - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java deleted file mode 100644 index c6d834bf9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/ProviderHelperStub.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.sufficientlysecure.keychain.testsupport; - -import android.content.Context; -import android.net.Uri; - -import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.ProviderHelper; - -/** - * Created by art on 21/06/14. - */ -class ProviderHelperStub extends ProviderHelper { -    public ProviderHelperStub(Context context) { -        super(context); -    } - -    @Override -    public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException { -        byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob")); -        return new WrappedPublicKeyRing(data, false, 0); -    } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java deleted file mode 100644 index 9e6ede761..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/TestDataUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.sufficientlysecure.keychain.testsupport; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; - -/** - * Misc support functions. Would just use Guava / Apache Commons but - * avoiding extra dependencies. - */ -public class TestDataUtil { -    public static byte[] readFully(InputStream input) { -        ByteArrayOutputStream output = new ByteArrayOutputStream(); -        appendToOutput(input, output); -        return output.toByteArray(); -    } - -    private static void appendToOutput(InputStream input, ByteArrayOutputStream output) { -        byte[] buffer = new byte[8192]; -        int bytesRead; -        try { -            while ((bytesRead = input.read(buffer)) != -1) { -                output.write(buffer, 0, bytesRead); -            } -        } catch (IOException e) { -            throw new RuntimeException(e); -        } -    } - -    public static byte[] readAllFully(Collection<String> inputResources) { -        ByteArrayOutputStream output = new ByteArrayOutputStream(); - -        for (String inputResource : inputResources) { -            appendToOutput(getResourceAsStream(inputResource), output); -        } -        return output.toByteArray(); -    } - - -    public static InputStream getResourceAsStream(String resourceName) { -        return TestDataUtil.class.getResourceAsStream(resourceName); -    } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/package-info.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/package-info.java deleted file mode 100644 index 1cc0f9a95..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/testsupport/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Test support classes. - * This is only in main code because of gradle-Android Studio-robolectric issues. Having - * classes in main code means IDE autocomplete, class detection, etc., all works. - * TODO Move into test package when possible - */ -package org.sufficientlysecure.keychain.testsupport; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java new file mode 100644 index 000000000..d2073d9a7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.support.v7.app.ActionBarActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.EditText; + +import org.spongycastle.bcpg.sig.KeyFlags; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ContactHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; + +import java.util.regex.Matcher; + +public class CreateKeyActivity extends ActionBarActivity { + +    AutoCompleteTextView nameEdit; +    AutoCompleteTextView emailEdit; +    EditText passphraseEdit; +    Button createButton; + +    @Override +    protected void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState); + +        setContentView(R.layout.create_key_activity); + +        nameEdit = (AutoCompleteTextView) findViewById(R.id.name); +        emailEdit = (AutoCompleteTextView) findViewById(R.id.email); +        passphraseEdit = (EditText) findViewById(R.id.passphrase); +        createButton = (Button) findViewById(R.id.create_key_button); + +        emailEdit.setThreshold(1); // Start working from first character +        emailEdit.setAdapter( +                new ArrayAdapter<String> +                        (this, android.R.layout.simple_spinner_dropdown_item, +                                ContactHelper.getPossibleUserEmails(this) +                        ) +        ); +        emailEdit.addTextChangedListener(new TextWatcher() { +            @Override +            public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { +            } + +            @Override +            public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { +            } + +            @Override +            public void afterTextChanged(Editable editable) { +                String email = editable.toString(); +                if (email.length() > 0) { +                    Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); +                    if (emailMatcher.matches()) { +                        emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, +                                R.drawable.uid_mail_ok, 0); +                    } else { +                        emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, +                                R.drawable.uid_mail_bad, 0); +                    } +                } else { +                    // remove drawable if email is empty +                    emailEdit.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); +                } +            } +        }); +        nameEdit.setThreshold(1); // Start working from first character +        nameEdit.setAdapter( +                new ArrayAdapter<String> +                        (this, android.R.layout.simple_spinner_dropdown_item, +                                ContactHelper.getPossibleUserNames(this) +                        ) +        ); + +        createButton.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                createKeyCheck(); +            } +        }); + +    } + +    private void createKeyCheck() { +        if (isEditTextNotEmpty(this, nameEdit) +                && isEditTextNotEmpty(this, emailEdit) +                && isEditTextNotEmpty(this, passphraseEdit)) { +            createKey(); +        } +    } + +    private void createKey() { +        Intent intent = new Intent(this, KeychainIntentService.class); +        intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING); + +        // Message is received after importing is done in KeychainIntentService +        KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( +                this, +                getString(R.string.progress_importing), +                ProgressDialog.STYLE_HORIZONTAL) { +            public void handleMessage(Message message) { +                // handle messages by standard KeychainIntentServiceHandler first +                super.handleMessage(message); + +                if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { +                    CreateKeyActivity.this.finish(); +                } +            } +        }; + +        // fill values for this action +        Bundle data = new Bundle(); + +        SaveKeyringParcel parcel = new SaveKeyringParcel(); +        parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.CERTIFY_OTHER, null)); +        parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.SIGN_DATA, null)); +        parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Constants.choice.algorithm.rsa, 4096, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, null)); +        String userId = nameEdit.getText().toString() + " <" + emailEdit.getText().toString() + ">"; +        parcel.mAddUserIds.add(userId); +        parcel.mNewPassphrase = passphraseEdit.getText().toString(); + +        // get selected key entries +        data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel); + +        intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + +        // Create a new Messenger for the communication back +        Messenger messenger = new Messenger(saveHandler); +        intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + +        saveHandler.showProgressDialog(this); + +        startService(intent); +    } + +    /** +     * Checks if text of given EditText is not empty. If it is empty an error is +     * set and the EditText gets the focus. +     * +     * @param context +     * @param editText +     * @return true if EditText is not empty +     */ +    private static boolean isEditTextNotEmpty(Context context, EditText editText) { +        boolean output = true; +        if (editText.getText().toString().length() == 0) { +            editText.setError("empty!"); +            editText.requestFocus(); +            output = false; +        } else { +            editText.setError(null); +        } + +        return output; +    } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java index 51457cd45..70ccb8800 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivityOld.java @@ -280,7 +280,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe                  Uri secretUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);                  WrappedSecretKeyRing keyRing = new ProviderHelper(this).getWrappedSecretKeyRing(secretUri); -                mMasterCanSign = keyRing.getSubKey().canCertify(); +                mMasterCanSign = keyRing.getSecretKey().canCertify();                  for (WrappedSecretKey key : keyRing.secretKeyIterator()) {                      // Turn into uncached instance                      mKeys.add(key.getUncached()); @@ -288,7 +288,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe                  }                  boolean isSet = false; -                for (String userId : keyRing.getSubKey().getUserIds()) { +                for (String userId : keyRing.getSecretKey().getUserIds()) {                      Log.d(Constants.TAG, "Added userId " + userId);                      if (!isSet) {                          isSet = true; @@ -556,7 +556,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe              saveParams.deletedKeys = mKeysView.getDeletedKeys();              saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);              saveParams.keysUsages = getKeysUsages(mKeysView); -            saveParams.newPassphrase = mNewPassphrase; +            saveParams.mNewPassphrase = mNewPassphrase;              saveParams.oldPassphrase = mCurrentPassphrase;              saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());              saveParams.keys = getKeys(mKeysView); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 94ca883d6..b41871a39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -228,7 +228,7 @@ public class EditKeyFragment extends LoaderFragment implements              }          }); -        mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.addSubKeys); +        mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys);          mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);          // Prepare the loaders. Either re-connect with an existing ones, @@ -298,7 +298,7 @@ public class EditKeyFragment extends LoaderFragment implements                      Bundle data = message.getData();                      // cache new returned passphrase! -                    mSaveKeyringParcel.newPassphrase = data +                    mSaveKeyringParcel.mNewPassphrase = data                              .getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);                  }              } @@ -320,19 +320,19 @@ public class EditKeyFragment extends LoaderFragment implements                  switch (message.what) {                      case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID:                          // toggle -                        if (mSaveKeyringParcel.changePrimaryUserId != null -                                && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) { -                            mSaveKeyringParcel.changePrimaryUserId = null; +                        if (mSaveKeyringParcel.mChangePrimaryUserId != null +                                && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { +                            mSaveKeyringParcel.mChangePrimaryUserId = null;                          } else { -                            mSaveKeyringParcel.changePrimaryUserId = userId; +                            mSaveKeyringParcel.mChangePrimaryUserId = userId;                          }                          break;                      case EditUserIdDialogFragment.MESSAGE_REVOKE:                          // toggle -                        if (mSaveKeyringParcel.revokeUserIds.contains(userId)) { -                            mSaveKeyringParcel.revokeUserIds.remove(userId); +                        if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) { +                            mSaveKeyringParcel.mRevokeUserIds.remove(userId);                          } else { -                            mSaveKeyringParcel.revokeUserIds.add(userId); +                            mSaveKeyringParcel.mRevokeUserIds.add(userId);                          }                          break;                  } @@ -363,10 +363,10 @@ public class EditKeyFragment extends LoaderFragment implements                          break;                      case EditSubkeyDialogFragment.MESSAGE_REVOKE:                          // toggle -                        if (mSaveKeyringParcel.revokeSubKeys.contains(keyId)) { -                            mSaveKeyringParcel.revokeSubKeys.remove(keyId); +                        if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) { +                            mSaveKeyringParcel.mRevokeSubKeys.remove(keyId);                          } else { -                            mSaveKeyringParcel.revokeSubKeys.add(keyId); +                            mSaveKeyringParcel.mRevokeSubKeys.add(keyId);                          }                          break;                  } @@ -450,10 +450,11 @@ public class EditKeyFragment extends LoaderFragment implements      }      private void save(String passphrase) { -        Log.d(Constants.TAG, "add userids to parcel: " + mUserIdsAddedAdapter.getDataAsStringList()); -        Log.d(Constants.TAG, "mSaveKeyringParcel.newPassphrase: " + mSaveKeyringParcel.newPassphrase); +        mSaveKeyringParcel.mAddUserIds = mUserIdsAddedAdapter.getDataAsStringList(); -        mSaveKeyringParcel.addUserIds = mUserIdsAddedAdapter.getDataAsStringList(); +        Log.d(Constants.TAG, "mSaveKeyringParcel.mAddUserIds: " + mSaveKeyringParcel.mAddUserIds); +        Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase); +        Log.d(Constants.TAG, "mSaveKeyringParcel.mRevokeUserIds: " + mSaveKeyringParcel.mRevokeUserIds);          // Message is received after importing is done in KeychainIntentService          KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( @@ -509,4 +510,4 @@ public class EditKeyFragment extends LoaderFragment implements          // start service with intent          getActivity().startService(intent);      } -}
\ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java index 51963e963..dc0510189 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java @@ -198,7 +198,7 @@ public class EncryptAsymmetricFragment extends Fragment {              String[] userId;              try {                  userId = mProviderHelper.getCachedPublicKeyRing( -                        KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserId(); +                        KeyRings.buildUnifiedKeyRingUri(mSecretKeyId)).getSplitPrimaryUserIdWithFallback();              } catch (PgpGeneralException e) {                  userId = null;              } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java new file mode 100644 index 000000000..aa4120d2c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/FirstTimeActivity.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; +import android.view.View; +import android.view.Window; +import android.widget.Button; + +import org.sufficientlysecure.keychain.R; + +public class FirstTimeActivity extends ActionBarActivity { + +    Button mCreateKey; +    Button mImportKey; +    Button mSkipSetup; + +    @Override +    protected void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState); + +        supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + +        setContentView(R.layout.first_time_activity); + +        mCreateKey = (Button) findViewById(R.id.first_time_create_key); +        mImportKey = (Button) findViewById(R.id.first_time_import_key); +        mSkipSetup = (Button) findViewById(R.id.first_time_cancel); + +        mSkipSetup.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                Intent intent = new Intent(FirstTimeActivity.this, KeyListActivity.class); +                startActivity(intent); +                finish(); +            } +        }); + +        mImportKey.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                Intent intent = new Intent(FirstTimeActivity.this, ImportKeysActivity.class); +                intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); +                startActivity(intent); +                finish(); +            } +        }); + +        mCreateKey.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                Intent intent = new Intent(FirstTimeActivity.this, CreateKeyActivity.class); +                startActivity(intent); +                finish(); +            } +        }); + +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java index 849576284..9cc623c83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java @@ -32,8 +32,7 @@ import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.Constants.choice.algorithm;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.helper.ExportHelper; -import org.sufficientlysecure.keychain.helper.OtherHelper; -import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; +import org.sufficientlysecure.keychain.helper.Preferences;  import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.KeychainDatabase;  import org.sufficientlysecure.keychain.service.KeychainIntentService; @@ -43,7 +42,6 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;  import org.sufficientlysecure.keychain.util.Log;  import java.io.IOException; -import java.util.ArrayList;  public class KeyListActivity extends DrawerActivity { @@ -53,6 +51,15 @@ public class KeyListActivity extends DrawerActivity {      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); +        Preferences prefs = Preferences.getPreferences(this); +        if (prefs.getFirstTime()) { +            prefs.setFirstTime(false); +            Intent intent = new Intent(this, FirstTimeActivity.class); +            startActivity(intent); +            finish(); +            return; +        } +          mExportHelper = new ExportHelper(this);          setContentView(R.layout.key_list_activity); @@ -66,9 +73,10 @@ public class KeyListActivity extends DrawerActivity {          super.onCreateOptionsMenu(menu);          getMenuInflater().inflate(R.menu.key_list, menu); -        if(Constants.DEBUG) { +        if (Constants.DEBUG) {              menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);              menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); +            menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);          }          return true; @@ -85,10 +93,6 @@ public class KeyListActivity extends DrawerActivity {                  createKey();                  return true; -            case R.id.menu_key_list_create_expert: -                createKeyExpert(); -                return true; -              case R.id.menu_key_list_export:                  mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);                  return true; @@ -98,7 +102,7 @@ public class KeyListActivity extends DrawerActivity {                      KeychainDatabase.debugRead(this);                      AppMsg.makeText(this, "Restored from backup", AppMsg.STYLE_CONFIRM).show();                      getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null); -                } catch(IOException e) { +                } catch (IOException e) {                      Log.e(Constants.TAG, "IO Error", e);                      AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();                  } @@ -108,12 +112,18 @@ public class KeyListActivity extends DrawerActivity {                  try {                      KeychainDatabase.debugWrite(this);                      AppMsg.makeText(this, "Backup successful", AppMsg.STYLE_CONFIRM).show(); -                } catch(IOException e) { +                } catch (IOException e) {                      Log.e(Constants.TAG, "IO Error", e);                      AppMsg.makeText(this, "IO Error: " + e.getMessage(), AppMsg.STYLE_ALERT).show();                  }                  return true; +            case R.id.menu_key_list_debug_first_time: +                Intent intent = new Intent(this, FirstTimeActivity.class); +                startActivity(intent); +                finish(); +                return true; +              default:                  return super.onOptionsItemSelected(item);          } @@ -125,50 +135,8 @@ public class KeyListActivity extends DrawerActivity {      }      private void createKey() { -        Intent intent = new Intent(this, WizardActivity.class); -//        intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); -//        intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); -//        intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view -        startActivityForResult(intent, 0); +        Intent intent = new Intent(this, CreateKeyActivity.class); +        startActivity(intent);      } -    private void createKeyExpert() { -        Intent intent = new Intent(this, KeychainIntentService.class); -        intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING); - -        // Message is received after importing is done in KeychainIntentService -        KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( -                this, -                getString(R.string.progress_importing), -                ProgressDialog.STYLE_HORIZONTAL) { -            public void handleMessage(Message message) { -                // handle messages by standard KeychainIntentServiceHandler first -                super.handleMessage(message); -                Bundle data = message.getData(); -                // OtherHelper.logDebugBundle(data, "message reply"); -            } -        }; - -        // fill values for this action -        Bundle data = new Bundle(); - -        SaveKeyringParcel parcel = new SaveKeyringParcel(); -        parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null)); -        parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null)); -        parcel.addUserIds.add("swagerinho"); -        parcel.newPassphrase = "swag"; - -        // get selected key entries -        data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel); - -        intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - -        // Create a new Messenger for the communication back -        Messenger messenger = new Messenger(saveHandler); -        intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - -        saveHandler.showProgressDialog(this); - -        startService(intent); -    }  }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java index c7fffe263..cfdea0611 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java @@ -149,8 +149,8 @@ public class ViewCertActivity extends ActionBarActivity                          providerHelper.getWrappedPublicKeyRing(sig.getKeyId());                  try { -                    sig.init(signerRing.getSubkey()); -                    if (sig.verifySignature(signeeRing.getSubkey(), signeeUid)) { +                    sig.init(signerRing.getPublicKey()); +                    if (sig.verifySignature(signeeRing.getPublicKey(), signeeUid)) {                          mStatus.setText(R.string.cert_verify_ok);                          mStatus.setTextColor(getResources().getColor(R.color.result_green));                      } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java deleted file mode 100644 index 601fc09f9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/WizardActivity.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program.  If not, see <http://www.gnu.org/licenses/>. - */ - -package org.sufficientlysecure.keychain.ui; - -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.ActionBarActivity; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Patterns; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RadioGroup; -import android.widget.TextView; - -import org.sufficientlysecure.htmltextview.HtmlTextView; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ContactHelper; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.regex.Matcher; - - -public class WizardActivity extends ActionBarActivity { - -    private State mCurrentState; - -    // values for mCurrentScreen -    private enum State { -        START, CREATE_KEY, IMPORT_KEY, K9 -    } - -    public static final int REQUEST_CODE_IMPORT = 0x00007703; - -    Button mBackButton; -    Button mNextButton; -    StartFragment mStartFragment; -    CreateKeyFragment mCreateKeyFragment; -    K9Fragment mK9Fragment; - -    private static final String K9_PACKAGE = "com.fsck.k9"; -    //    private static final String K9_MARKET_INTENT_URI_BASE = "market://details?id=%s"; -//    private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse( -//            String.format(K9_MARKET_INTENT_URI_BASE, K9_PACKAGE))); -    private static final Intent K9_MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/k9mail/k-9/releases/tag/4.904")); - -    LinearLayout mProgressLayout; -    View mProgressLine; -    ProgressBar mProgressBar; -    ImageView mProgressImage; -    TextView mProgressText; - -    /** -     * Checks if text of given EditText is not empty. If it is empty an error is -     * set and the EditText gets the focus. -     * -     * @param context -     * @param editText -     * @return true if EditText is not empty -     */ -    private static boolean isEditTextNotEmpty(Context context, EditText editText) { -        boolean output = true; -        if (editText.getText().toString().length() == 0) { -            editText.setError("empty!"); -            editText.requestFocus(); -            output = false; -        } else { -            editText.setError(null); -        } - -        return output; -    } - -    public static class StartFragment extends Fragment { -        public static StartFragment newInstance() { -            StartFragment myFragment = new StartFragment(); - -            Bundle args = new Bundle(); -            myFragment.setArguments(args); - -            return myFragment; -        } - -        @Override -        public View onCreateView(LayoutInflater inflater, ViewGroup container, -                                 Bundle savedInstanceState) { -            return inflater.inflate(R.layout.wizard_start_fragment, -                    container, false); -        } -    } - -    public static class CreateKeyFragment extends Fragment { -        public static CreateKeyFragment newInstance() { -            CreateKeyFragment myFragment = new CreateKeyFragment(); - -            Bundle args = new Bundle(); -            myFragment.setArguments(args); - -            return myFragment; -        } - -        @Override -        public View onCreateView(LayoutInflater inflater, ViewGroup container, -                                 Bundle savedInstanceState) { -            View view = inflater.inflate(R.layout.wizard_create_key_fragment, -                    container, false); - -            final AutoCompleteTextView emailView = (AutoCompleteTextView) view.findViewById(R.id.email); -            emailView.setThreshold(1); // Start working from first character -            emailView.setAdapter( -                    new ArrayAdapter<String> -                            (getActivity(), android.R.layout.simple_spinner_dropdown_item, -                                    ContactHelper.getPossibleUserEmails(getActivity()) -                            ) -            ); -            emailView.addTextChangedListener(new TextWatcher() { -                @Override -                public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { -                } - -                @Override -                public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { -                } - -                @Override -                public void afterTextChanged(Editable editable) { -                    String email = editable.toString(); -                    if (email.length() > 0) { -                        Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); -                        if (emailMatcher.matches()) { -                            emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, -                                    R.drawable.uid_mail_ok, 0); -                        } else { -                            emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, -                                    R.drawable.uid_mail_bad, 0); -                        } -                    } else { -                        // remove drawable if email is empty -                        emailView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); -                    } -                } -            }); -            final AutoCompleteTextView nameView = (AutoCompleteTextView) view.findViewById(R.id.name); -            nameView.setThreshold(1); // Start working from first character -            nameView.setAdapter( -                    new ArrayAdapter<String> -                            (getActivity(), android.R.layout.simple_spinner_dropdown_item, -                                    ContactHelper.getPossibleUserNames(getActivity()) -                            ) -            ); -            return view; -        } -    } - -    public static class K9Fragment extends Fragment { -        public static K9Fragment newInstance() { -            K9Fragment myFragment = new K9Fragment(); - -            Bundle args = new Bundle(); -            myFragment.setArguments(args); - -            return myFragment; -        } - -        @Override -        public View onCreateView(LayoutInflater inflater, ViewGroup container, -                                 Bundle savedInstanceState) { -            View v = inflater.inflate(R.layout.wizard_k9_fragment, -                    container, false); - -            HtmlTextView text = (HtmlTextView) v -                    .findViewById(R.id.wizard_k9_text); -            text.setHtmlFromString("Install K9. It's good for you! Here is a screenhot how to enable OK in K9: (TODO)", true); - -            return v; -        } - -    } - -    /** -     * Loads new fragment -     * -     * @param fragment -     */ -    private void loadFragment(Fragment fragment) { -        FragmentManager fragmentManager = getSupportFragmentManager(); -        FragmentTransaction fragmentTransaction = fragmentManager -                .beginTransaction(); -        fragmentTransaction.replace(R.id.wizard_container, -                fragment); -        fragmentTransaction.commit(); -    } - -    /** -     * Instantiate View and initialize fragments for this Activity -     */ -    @Override -    protected void onCreate(Bundle savedInstanceState) { -        super.onCreate(savedInstanceState); - -        setContentView(R.layout.wizard_activity); -        mBackButton = (Button) findViewById(R.id.wizard_back); -        mNextButton = (Button) findViewById(R.id.wizard_next); - -        // progress layout -        mProgressLayout = (LinearLayout) findViewById(R.id.wizard_progress); -        mProgressLine = findViewById(R.id.wizard_progress_line); -        mProgressBar = (ProgressBar) findViewById(R.id.wizard_progress_progressbar); -        mProgressImage = (ImageView) findViewById(R.id.wizard_progress_image); -        mProgressText = (TextView) findViewById(R.id.wizard_progress_text); - -        changeToState(State.START); -    } - -    private enum ProgressState { -        WORKING, ENABLED, DISABLED, ERROR -    } - -    private void showProgress(ProgressState state, String text) { -        switch (state) { -            case WORKING: -                mProgressBar.setVisibility(View.VISIBLE); -                mProgressImage.setVisibility(View.GONE); -                break; -            case ENABLED: -                mProgressBar.setVisibility(View.GONE); -                mProgressImage.setVisibility(View.VISIBLE); -//			mProgressImage.setImageDrawable(getResources().getDrawable( -//					R.drawable.status_enabled)); -                break; -            case DISABLED: -                mProgressBar.setVisibility(View.GONE); -                mProgressImage.setVisibility(View.VISIBLE); -//			mProgressImage.setImageDrawable(getResources().getDrawable( -//					R.drawable.status_disabled)); -                break; -            case ERROR: -                mProgressBar.setVisibility(View.GONE); -                mProgressImage.setVisibility(View.VISIBLE); -//			mProgressImage.setImageDrawable(getResources().getDrawable( -//					R.drawable.status_fail)); -                break; - -            default: -                break; -        } -        mProgressText.setText(text); - -        mProgressLine.setVisibility(View.VISIBLE); -        mProgressLayout.setVisibility(View.VISIBLE); -    } - -    private void hideProgress() { -        mProgressLine.setVisibility(View.GONE); -        mProgressLayout.setVisibility(View.GONE); -    } - -    public void nextOnClick(View view) { -        // close keyboard -        if (getCurrentFocus() != null) { -            InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); -            inputManager.hideSoftInputFromWindow(getCurrentFocus() -                    .getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); -        } - -        switch (mCurrentState) { -            case START: { -                RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_start_radio_group); -                int selectedId = radioGroup.getCheckedRadioButtonId(); -                switch (selectedId) { -                    case R.id.wizard_start_new_key: { -                        changeToState(State.CREATE_KEY); -                        break; -                    } -                    case R.id.wizard_start_import: { -                        changeToState(State.IMPORT_KEY); -                        break; -                    } -                    case R.id.wizard_start_skip: { -                        finish(); -                        break; -                    } -                } - -                mBackButton.setText(R.string.btn_back); -                break; -            } -            case CREATE_KEY: -                EditText nameEdit = (EditText) findViewById(R.id.name); -                EditText emailEdit = (EditText) findViewById(R.id.email); -                EditText passphraseEdit = (EditText) findViewById(R.id.passphrase); - -                if (isEditTextNotEmpty(this, nameEdit) -                        && isEditTextNotEmpty(this, emailEdit) -                        && isEditTextNotEmpty(this, passphraseEdit)) { - -//                    SaveKeyringParcel newKey = new SaveKeyringParcel(); -//                    newKey.addUserIds.add(nameEdit.getText().toString() + " <" -//                            + emailEdit.getText().toString() + ">"); - - -                    AsyncTask<String, Boolean, Boolean> generateTask = new AsyncTask<String, Boolean, Boolean>() { - -                        @Override -                        protected void onPreExecute() { -                            super.onPreExecute(); - -                            showProgress(ProgressState.WORKING, "generating key..."); -                        } - -                        @Override -                        protected Boolean doInBackground(String... params) { -                            return true; -                        } - -                        @Override -                        protected void onPostExecute(Boolean result) { -                            super.onPostExecute(result); - -                            if (result) { -                                showProgress(ProgressState.ENABLED, "key generated successfully!"); - -                                changeToState(State.K9); -                            } else { -                                showProgress(ProgressState.ERROR, "error in key gen"); -                            } -                        } - -                    }; - -                    generateTask.execute(""); -                } -                break; -            case K9: { -                RadioGroup radioGroup = (RadioGroup) findViewById(R.id.wizard_k9_radio_group); -                int selectedId = radioGroup.getCheckedRadioButtonId(); -                switch (selectedId) { -                    case R.id.wizard_k9_install: { -                        try { -                            startActivity(K9_MARKET_INTENT); -                        } catch (ActivityNotFoundException e) { -                            Log.e(Constants.TAG, "Activity not found for: " + K9_MARKET_INTENT); -                        } -                        break; -                    } -                    case R.id.wizard_k9_skip: { -                        finish(); -                        break; -                    } -                } - -                finish(); -                break; -            } -            default: -                break; -        } -    } - -    @Override -    protected void onActivityResult(int requestCode, int resultCode, Intent data) { -        super.onActivityResult(requestCode, resultCode, data); -        switch (requestCode) { -            case REQUEST_CODE_IMPORT: { -                if (resultCode == Activity.RESULT_OK) { -                    // imported now... -                    changeToState(State.K9); -                } else { -                    // back to start -                    changeToState(State.START); -                } -                break; -            } - -            default: { -                super.onActivityResult(requestCode, resultCode, data); - -                break; -            } -        } -    } - -    public void backOnClick(View view) { -        switch (mCurrentState) { -            case START: -                finish(); -                break; -            case CREATE_KEY: -                changeToState(State.START); -                break; -            case IMPORT_KEY: -                changeToState(State.START); -                break; -            default: -                changeToState(State.START); -                break; -        } -    } - -    private void changeToState(State state) { -        switch (state) { -            case START: { -                mCurrentState = State.START; -                mStartFragment = StartFragment.newInstance(); -                loadFragment(mStartFragment); -                mBackButton.setText(android.R.string.cancel); -                mNextButton.setText(R.string.btn_next); -                break; -            } -            case CREATE_KEY: { -                mCurrentState = State.CREATE_KEY; -                mCreateKeyFragment = CreateKeyFragment.newInstance(); -                loadFragment(mCreateKeyFragment); -                break; -            } -            case IMPORT_KEY: { -                mCurrentState = State.IMPORT_KEY; -                Intent intent = new Intent(this, ImportKeysActivity.class); -                intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); -                startActivityForResult(intent, REQUEST_CODE_IMPORT); -                break; -            } -            case K9: { -                mCurrentState = State.K9; -                mBackButton.setEnabled(false); // don't go back to import/create key -                mK9Fragment = K9Fragment.newInstance(); -                loadFragment(mK9Fragment); -                break; -            } -        } -    } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index ccc5960c8..e5dbebe01 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -143,7 +143,7 @@ public class SubkeysAdapter extends CursorAdapter {          // for edit key          if (mSaveKeyringParcel != null) { -            boolean revokeThisSubkey = (mSaveKeyringParcel.revokeSubKeys.contains(keyId)); +            boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));              if (revokeThisSubkey) {                  if (!isRevoked) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 10e299ae3..18312660a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -131,10 +131,10 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC          // for edit key          if (mSaveKeyringParcel != null) { -            boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null); -            boolean changeThisPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null -                    && mSaveKeyringParcel.changePrimaryUserId.equals(userId)); -            boolean revokeThisUserId = (mSaveKeyringParcel.revokeUserIds.contains(userId)); +            boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null); +            boolean changeThisPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null +                    && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)); +            boolean revokeThisUserId = (mSaveKeyringParcel.mRevokeUserIds.contains(userId));              // only if primary user id will be changed              // (this is not triggered if the user id is currently the primary one) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index 4d0b73d30..d723f88af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -152,7 +152,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor                  // above can't be statically verified to have been set in all cases because                  // the catch clause doesn't return.                  try { -                    userId = secretRing.getPrimaryUserId(); +                    userId = secretRing.getPrimaryUserIdWithFallback();                  } catch (PgpGeneralException e) {                      userId = null;                  } @@ -232,7 +232,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor                  try {                      PassphraseCacheService.addCachedPassphrase(activity, masterKeyId, passphrase, -                        secretRing.getPrimaryUserId()); +                        secretRing.getPrimaryUserIdWithFallback());                  } catch(PgpGeneralException e) {                      Log.e(Constants.TAG, "adding of a passhrase failed", e);                  } @@ -240,7 +240,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor                  if (unlockedSecretKey.getKeyId() != masterKeyId) {                      PassphraseCacheService.addCachedPassphrase(                              activity, unlockedSecretKey.getKeyId(), passphrase, -                            unlockedSecretKey.getPrimaryUserId()); +                            unlockedSecretKey.getPrimaryUserIdWithFallback());                  }                  // also return passphrase back to activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java index 5553ea5d2..869eea03f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressScaler.java @@ -39,15 +39,21 @@ public class ProgressScaler implements Progressable {       * Set progress of ProgressDialog by sending message to handler on UI thread       */      public void setProgress(String message, int progress, int max) { -        mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax); +        if (mWrapped != null) { +            mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax); +        }      }      public void setProgress(int resourceId, int progress, int max) { -        mWrapped.setProgress(resourceId, progress, mMax); +        if (mWrapped != null) { +            mWrapped.setProgress(resourceId, progress, mMax); +        }      }      public void setProgress(int progress, int max) { -        mWrapped.setProgress(progress, max); +        if (mWrapped != null) { +            mWrapped.setProgress(progress, max); +        }      }  } diff --git a/OpenKeychain/src/main/res/drawable/first_time_1.png b/OpenKeychain/src/main/res/drawable/first_time_1.pngBinary files differ new file mode 100644 index 000000000..1f340df5c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable/first_time_1.png diff --git a/OpenKeychain/src/main/res/layout/wizard_create_key_fragment.xml b/OpenKeychain/src/main/res/layout/create_key_activity.xml index 258ea7223..673f43084 100644 --- a/OpenKeychain/src/main/res/layout/wizard_create_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/create_key_activity.xml @@ -2,6 +2,7 @@  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="wrap_content" +    android:padding="8dp"      android:orientation="vertical">      <TextView @@ -38,4 +39,16 @@          android:layout_gravity="center_horizontal" /> +    <Button +        android:id="@+id/create_key_button" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:layout_weight="1" +        android:layout_gravity="center_horizontal" +        android:layout_margin="8dp" +        android:text="@string/first_time_create_key" +        android:background="@drawable/button_edgy" +        android:drawableLeft="@drawable/ic_action_new_account" /> + +  </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/first_time_activity.xml b/OpenKeychain/src/main/res/layout/first_time_activity.xml new file mode 100644 index 000000000..514f34212 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/first_time_activity.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +    android:layout_width="wrap_content" +    android:layout_height="wrap_content" +    android:paddingTop="16dp" +    android:paddingBottom="8dp"> + +    <LinearLayout +        android:layout_width="match_parent" +        android:layout_height="match_parent" +        android:orientation="vertical"> + +        <TextView +            android:layout_width="wrap_content" +            android:layout_height="wrap_content" +            android:layout_marginLeft="8dp" +            android:textAppearance="?android:attr/textAppearanceLarge" +            android:text="@string/app_name" +            android:drawableLeft="@drawable/ic_launcher" +            android:drawablePadding="16dp" +            android:gravity="center" +            android:layout_gravity="center_horizontal" /> + +        <ImageView +            android:layout_width="wrap_content" +            android:layout_marginLeft="64dp" +            android:layout_marginRight="64dp" +            android:layout_marginTop="16dp" +            android:layout_marginBottom="16dp" +            android:layout_height="256dp" +            android:src="@drawable/first_time_1" /> + +        <TextView +            android:layout_width="wrap_content" +            android:layout_height="wrap_content" +            android:layout_marginLeft="64dp" +            android:layout_marginRight="64dp" +            android:textAppearance="?android:attr/textAppearanceMedium" +            android:text="@string/first_time_text1" +            android:layout_gravity="center_horizontal" +            android:gravity="center_horizontal" /> + + +    </LinearLayout> + +    <Button +        android:id="@+id/first_time_cancel" +        android:layout_alignParentBottom="true" +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:layout_marginBottom="8dp" +        android:layout_marginLeft="8dp" +        android:layout_marginRight="8dp" +        android:text="@string/first_time_skip" +        android:background="@drawable/button_edgy" /> + +    <LinearLayout +        android:layout_width="match_parent" +        android:layout_height="wrap_content" +        android:layout_above="@id/first_time_cancel" +        android:orientation="horizontal"> + +        <Button +            android:id="@+id/first_time_create_key" +            android:layout_width="match_parent" +            android:layout_height="wrap_content" +            android:layout_gravity="center_horizontal" +            android:layout_weight="1" +            android:layout_marginLeft="8dp" +            android:layout_marginRight="8dp" +            android:text="@string/first_time_create_key" +            android:background="@drawable/button_edgy" +            android:drawableLeft="@drawable/ic_action_new_account" /> + +        <Button +            android:id="@+id/first_time_import_key" +            android:layout_width="match_parent" +            android:layout_height="wrap_content" +            android:layout_weight="1" +            android:layout_gravity="center_horizontal" +            android:layout_marginLeft="8dp" +            android:layout_marginRight="8dp" +            android:text="@string/first_time_import_key" +            android:background="@drawable/button_edgy" +            android:drawableLeft="@drawable/ic_action_download" /> +    </LinearLayout> + + +</RelativeLayout> diff --git a/OpenKeychain/src/main/res/layout/wizard_activity.xml b/OpenKeychain/src/main/res/layout/wizard_activity.xml deleted file mode 100644 index 299d07a76..000000000 --- a/OpenKeychain/src/main/res/layout/wizard_activity.xml +++ /dev/null @@ -1,98 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" -    android:layout_width="match_parent" -    android:layout_height="match_parent"> - -    <LinearLayout -        android:id="@+id/wizard_buttons" -        android:layout_width="match_parent" -        android:layout_height="wrap_content" -        android:layout_alignParentBottom="true" -        android:orientation="horizontal"> - -        <Button -            android:id="@+id/wizard_back" -            android:layout_width="match_parent" -            android:layout_height="wrap_content" -            android:layout_weight="1" -            android:onClick="backOnClick" -            android:text="cancel" -            style="@style/SelectableItem" /> - -        <View -            android:layout_width="1dip" -            android:layout_height="match_parent" -            android:layout_marginBottom="4dip" -            android:layout_marginTop="4dip" -            android:background="?android:attr/listDivider" /> - -        <Button -            android:id="@+id/wizard_next" -            android:layout_width="match_parent" -            android:layout_height="wrap_content" -            android:layout_weight="1" -            android:onClick="nextOnClick" -            android:text="next" -            style="@style/SelectableItem" /> -    </LinearLayout> - -    <View -        android:id="@+id/wizard_progress_line" -        android:layout_width="match_parent" -        android:layout_height="1dip" -        android:layout_above="@+id/wizard_buttons" -        android:layout_marginLeft="4dip" -        android:layout_marginRight="4dip" -        android:background="?android:attr/listDivider" -        android:visibility="gone" /> - -    <LinearLayout -        android:id="@+id/wizard_progress" -        android:layout_width="match_parent" -        android:layout_height="wrap_content" -        android:layout_above="@+id/wizard_progress_line" -        android:visibility="gone"> - -        <ProgressBar -            android:id="@+id/wizard_progress_progressbar" -            android:layout_width="wrap_content" -            android:layout_height="wrap_content" /> - -        <ImageView -            android:id="@+id/wizard_progress_image" -            android:layout_width="wrap_content" -            android:layout_height="wrap_content" -            android:src="@drawable/icon_light_refresh" /> - -        <TextView -            android:id="@+id/wizard_progress_text" -            android:layout_width="wrap_content" -            android:layout_height="wrap_content" -            android:layout_gravity="center_vertical" -            android:text="asd" -            android:textAppearance="?android:attr/textAppearanceMedium" /> -    </LinearLayout> - -    <View -        android:id="@+id/wizard_line2" -        android:layout_width="match_parent" -        android:layout_height="1dip" -        android:layout_above="@+id/wizard_progress" -        android:layout_marginLeft="4dip" -        android:layout_marginRight="4dip" -        android:background="?android:attr/listDivider" /> - -    <ScrollView -        android:layout_width="match_parent" -        android:layout_height="match_parent" -        android:layout_above="@+id/wizard_line2"> - -        <LinearLayout -            android:id="@+id/wizard_container" -            android:layout_width="match_parent" -            android:layout_height="wrap_content" -            android:orientation="vertical" -            android:padding="16dp" /> -    </ScrollView> - -</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/wizard_k9_fragment.xml b/OpenKeychain/src/main/res/layout/wizard_k9_fragment.xml deleted file mode 100644 index 342adc37e..000000000 --- a/OpenKeychain/src/main/res/layout/wizard_k9_fragment.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" -    android:layout_width="match_parent" -    android:layout_height="wrap_content" -    android:orientation="vertical" > - -    <org.sufficientlysecure.htmltextview.HtmlTextView -        android:id="@+id/wizard_k9_text" -        android:layout_width="match_parent" -        android:layout_height="wrap_content" -        android:paddingBottom="4dp" -        android:text="Text..." -        android:textAppearance="?android:attr/textAppearanceMedium" /> - -    <RadioGroup -        android:id="@+id/wizard_k9_radio_group" -        android:layout_width="match_parent" -        android:layout_height="wrap_content"> - -        <RadioButton -            android:layout_width="match_parent" -            android:layout_height="?android:attr/listPreferredItemHeight" -            android:checked="true" -            android:textAppearance="?android:attr/textAppearanceMedium" -            style="@style/SelectableItem" -            android:text="install K9" -            android:id="@+id/wizard_k9_install" /> - -        <View -            android:layout_width="match_parent" -            android:layout_height="1dip" -            android:background="?android:attr/listDivider" /> - -        <RadioButton -            android:layout_width="match_parent" -            android:layout_height="?android:attr/listPreferredItemHeight" -            android:textAppearance="?android:attr/textAppearanceMedium" -            style="@style/SelectableItem" -            android:text="skip install" -            android:id="@+id/wizard_k9_skip" /> -    </RadioGroup> - -</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/wizard_start_fragment.xml b/OpenKeychain/src/main/res/layout/wizard_start_fragment.xml deleted file mode 100644 index 9e1403f74..000000000 --- a/OpenKeychain/src/main/res/layout/wizard_start_fragment.xml +++ /dev/null @@ -1,63 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" -    android:layout_width="match_parent" -    android:layout_height="wrap_content" -    android:orientation="vertical"> - -    <TextView -        android:layout_width="match_parent" -        android:layout_height="wrap_content" -        android:paddingBottom="4dp" -        android:text="Welcome to OpenKeychain" -        android:textAppearance="?android:attr/textAppearanceMedium" /> - -    <TextView -        style="@style/SectionHeader" -        android:layout_width="wrap_content" -        android:layout_height="0dp" -        android:layout_marginTop="14dp" -        android:text="What you wanna do today?" -        android:layout_weight="1" /> - -    <RadioGroup -        android:id="@+id/wizard_start_radio_group" -        android:layout_width="match_parent" -        android:layout_height="wrap_content"> - -        <RadioButton -            android:layout_width="match_parent" -            android:layout_height="?android:attr/listPreferredItemHeight" -            android:checked="true" -            android:textAppearance="?android:attr/textAppearanceMedium" -            style="@style/SelectableItem" -            android:text="new key" -            android:id="@+id/wizard_start_new_key" /> - -        <View -            android:layout_width="match_parent" -            android:layout_height="1dip" -            android:background="?android:attr/listDivider" /> - -        <RadioButton -            android:layout_width="match_parent" -            android:layout_height="?android:attr/listPreferredItemHeight" -            android:textAppearance="?android:attr/textAppearanceMedium" -            style="@style/SelectableItem" -            android:text="import existing key" -            android:id="@+id/wizard_start_import" /> - -        <View -            android:layout_width="match_parent" -            android:layout_height="1dip" -            android:background="?android:attr/listDivider" /> - -        <RadioButton -            android:layout_width="match_parent" -            android:layout_height="?android:attr/listPreferredItemHeight" -            android:textAppearance="?android:attr/textAppearanceMedium" -            style="@style/SelectableItem" -            android:text="skip wizard" -            android:id="@+id/wizard_start_skip" /> -    </RadioGroup> - -</LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/menu/key_list.xml b/OpenKeychain/src/main/res/menu/key_list.xml index ebb7314b8..e865df182 100644 --- a/OpenKeychain/src/main/res/menu/key_list.xml +++ b/OpenKeychain/src/main/res/menu/key_list.xml @@ -27,11 +27,6 @@          android:title="@string/menu_create_key" />      <item -        android:id="@+id/menu_key_list_create_expert" -        app:showAsAction="never" -        android:title="@string/menu_create_key_expert" /> - -    <item          android:id="@+id/menu_key_list_debug_read"          app:showAsAction="never"          android:title="Debug / DB restore" @@ -43,4 +38,10 @@          android:title="Debug / DB backup"          android:visible="false" /> +    <item +        android:id="@+id/menu_key_list_debug_first_time" +        app:showAsAction="never" +        android:title="Debug / Show first time screen" +        android:visible="false" /> +  </menu> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 686460f75..50f73e73b 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -9,7 +9,6 @@      <string name="title_authentication">Passphrase</string>      <string name="title_create_key">Create Key</string>      <string name="title_edit_key">Edit Key</string> -    <string name="title_wizard">Welcome to OpenKeychain</string>      <string name="title_preferences">Preferences</string>      <string name="title_api_registered_apps">Apps</string>      <string name="title_key_server_preference">Keyserver Preference</string> @@ -511,7 +510,7 @@      <string name="cert_casual">casual</string>      <string name="cert_positive">positive</string>      <string name="cert_revoke">revoked</string> -    <string name="cert_verify_ok">ok</string> +    <string name="cert_verify_ok">OK</string>      <string name="cert_verify_failed">failed!</string>      <string name="cert_verify_error">error!</string>      <string name="cert_verify_unavailable">key unavailable</string> @@ -556,7 +555,7 @@      <string name="msg_ip_reinsert_secret">Re-inserting secret key</string>      <string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string>      <string name="msg_ip_uid_cert_error">Error processing certificate!</string> -    <string name="msg_ip_uid_cert_good">User id is certified by %1$s (%2$s)</string> +    <string name="msg_ip_uid_cert_good">User id is certified by %1$s</string>      <plurals name="msg_ip_uid_certs_unknown">          <item quantity="one">Ignoring one certificate issued by an unknown public key</item>          <item quantity="other">Ignoring %s certificates issued by unknown public keys</item> @@ -637,6 +636,17 @@      <string name="msg_mg_heterogeneous">Tried to consolidate heterogeneous keyrings</string>      <string name="msg_mg_new_subkey">Adding new subkey %s</string>      <string name="msg_mg_found_new">Found %s new certificates in keyring</string> +    <string name="msg_mg_unchanged">No new certificates</string> + +    <!-- createSecretKeyRing --> +    <string name="msg_cr">Generating new master key</string> +    <string name="msg_cr_error_no_master">No master key options specified!</string> +    <string name="msg_cr_error_no_user_id">Keyrings must be created with at least one user id!</string> +    <string name="msg_cr_error_no_certify">Master key must have certify flag!</string> +    <string name="msg_cr_error_keysize_512">Key size must be greater or equal 512!</string> +    <string name="msg_cr_error_internal_pgp">Internal PGP error!</string> +    <string name="msg_cr_error_unknown_algo">Bad algorithm choice!</string> +    <string name="msg_cr_error_master_elgamal">Master key must not be of type ElGamal!</string>      <!-- modifySecretKeyRing -->      <string name="msg_mr">Modifying keyring %s</string> @@ -644,10 +654,13 @@      <string name="msg_mf_error_fingerprint">Actual key fingerprint does not match the expected one!</string>      <string name="msg_mf_error_keyid">No key ID. This is an internal error, please file a bug report!</string>      <string name="msg_mf_error_integrity">Internal error, integrity check failed!</string> +    <string name="msg_mf_error_noexist_primary">Bad primary user id specified!</string>      <string name="msg_mf_error_revoked_primary">Revoked user ids cannot be primary!</string>      <string name="msg_mf_error_pgp">PGP internal exception!</string>      <string name="msg_mf_error_sig">Signature exception!</string>      <string name="msg_mf_passphrase">Changing passphrase</string> +    <string name="msg_mf_primary_replace_old">Replacing certificate of previous primary user id</string> +    <string name="msg_mf_primary_new">Generating new certificate for new primary user id</string>      <string name="msg_mf_subkey_change">Modifying subkey %s</string>      <string name="msg_mf_subkey_missing">Tried to operate on missing subkey %s!</string>      <string name="msg_mf_subkey_new">Generating new %1$s bit %2$s subkey</string> @@ -691,4 +704,10 @@      <string name="info_no_manual_account_creation">Do not create OpenKeychain-Accounts manually.\nFor more information, see Help.</string>      <string name="contact_show_key">Show key (%s)</string> +    <!-- First Time --> +    <string name="first_time_text1">Take back your privacy with OpenKeychain!</string> +    <string name="first_time_create_key">Create Key</string> +    <string name="first_time_import_key">Import Key</string> +    <string name="first_time_skip">Skip Setup</string> +  </resources> | 
