aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java470
1 files changed, 346 insertions, 124 deletions
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 6e45fab99..1a251eb79 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -34,6 +34,7 @@ import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
@@ -49,9 +50,10 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
-import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
+import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
+import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -133,7 +135,7 @@ public class PgpKeyOperation {
public PgpKeyOperation(Progressable progress) {
super();
if (progress != null) {
- mProgress = new Stack<Progressable>();
+ mProgress = new Stack<>();
mProgress.push(progress);
}
}
@@ -286,13 +288,11 @@ public class PgpKeyOperation {
// build new key pair
return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
- } catch(NoSuchProviderException e) {
+ } catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
log.add(LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
return null;
- } catch(InvalidAlgorithmParameterException e) {
- throw new RuntimeException(e);
} catch(PGPException e) {
Log.e(Constants.TAG, "internal pgp error", e);
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -300,7 +300,7 @@ public class PgpKeyOperation {
}
}
- public EditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) {
+ public PgpEditKeyResult createSecretKeyRing(SaveKeyringParcel saveParcel) {
OperationLog log = new OperationLog();
int indent = 0;
@@ -313,23 +313,23 @@ public class PgpKeyOperation {
if (saveParcel.mAddSubKeys.isEmpty()) {
log.add(LogType.MSG_CR_ERROR_NO_MASTER, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (saveParcel.mAddUserIds.isEmpty()) {
log.add(LogType.MSG_CR_ERROR_NO_USER_ID, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
SubkeyAdd add = saveParcel.mAddSubKeys.remove(0);
if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
log.add(LogType.MSG_CR_ERROR_NO_CERTIFY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (add.mExpiry == null) {
log.add(LogType.MSG_CR_ERROR_NULL_EXPIRY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
subProgressPush(10, 30);
@@ -338,7 +338,7 @@ public class PgpKeyOperation {
// return null if this failed (an error will already have been logged by createKey)
if (keyPair == null) {
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
progress(R.string.progress_building_master_key, 40);
@@ -365,10 +365,10 @@ public class PgpKeyOperation {
} catch (PGPException e) {
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
Log.e(Constants.TAG, "pgp error encoding key", e);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} catch (IOException e) {
Log.e(Constants.TAG, "io error encoding key", e);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
}
@@ -387,8 +387,11 @@ public class PgpKeyOperation {
* with a passphrase fails, the operation will fail with an unlocking error. More specific
* handling of errors should be done in UI code!
*
+ * If the passphrase is null, only a restricted subset of operations will be available,
+ * namely stripping of subkeys and changing the protection mode of dummy keys.
+ *
*/
- public EditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
+ public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
String passphrase) {
OperationLog log = new OperationLog();
@@ -413,7 +416,7 @@ public class PgpKeyOperation {
// Make sure this is called with a proper SaveKeyringParcel
if (saveParcel.mMasterKeyId == null || saveParcel.mMasterKeyId != wsKR.getMasterKeyId()) {
log.add(LogType.MSG_MF_ERROR_KEYID, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// We work on bouncycastle object level here
@@ -424,21 +427,26 @@ public class PgpKeyOperation {
if (saveParcel.mFingerprint == null || !Arrays.equals(saveParcel.mFingerprint,
masterSecretKey.getPublicKey().getFingerprint())) {
log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ // If we have no passphrase, only allow restricted operation
+ if (passphrase == null) {
+ return internalRestricted(sKR, saveParcel, log);
}
// read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER;
- long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L :
- masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds();
+ Date expiryTime = wsKR.getPublicKey().getExpiryTime();
+ long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
}
- private EditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
+ private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
int masterKeyFlags, long masterKeyExpiry,
SaveKeyringParcel saveParcel, String passphrase,
OperationLog log) {
@@ -460,7 +468,7 @@ public class PgpKeyOperation {
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
} catch (PGPException e) {
log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
}
@@ -469,7 +477,7 @@ public class PgpKeyOperation {
// Check if we were cancelled
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, indent);
- return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
}
{ // work on master secret key
@@ -477,7 +485,7 @@ public class PgpKeyOperation {
PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids
- subProgressPush(15, 25);
+ subProgressPush(15, 23);
for (int i = 0; i < saveParcel.mAddUserIds.size(); i++) {
progress(R.string.progress_modify_adduid, (i - 1) * (100 / saveParcel.mAddUserIds.size()));
@@ -486,7 +494,7 @@ public class PgpKeyOperation {
if (userId.equals("")) {
log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// this operation supersedes all previous binding and revocation certificates,
@@ -494,11 +502,11 @@ public class PgpKeyOperation {
@SuppressWarnings("unchecked")
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
if (it != null) {
- for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
+ for (PGPSignature cert : new IterableIterator<>(it)) {
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
// foreign certificate?! error error error
log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
@@ -521,8 +529,37 @@ public class PgpKeyOperation {
}
subProgressPop();
- // 2b. Add revocations for revoked user ids
- subProgressPush(25, 40);
+ // 2b. Add certificates for new user ids
+ subProgressPush(23, 32);
+ for (int i = 0; i < saveParcel.mAddUserAttribute.size(); i++) {
+
+ progress(R.string.progress_modify_adduat, (i - 1) * (100 / saveParcel.mAddUserAttribute.size()));
+ WrappedUserAttribute attribute = saveParcel.mAddUserAttribute.get(i);
+
+ switch (attribute.getType()) {
+ // the 'none' type must not succeed
+ case WrappedUserAttribute.UAT_NONE:
+ log.add(LogType.MSG_MF_UAT_ERROR_EMPTY, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ case WrappedUserAttribute.UAT_IMAGE:
+ log.add(LogType.MSG_MF_UAT_ADD_IMAGE, indent);
+ break;
+ default:
+ log.add(LogType.MSG_MF_UAT_ADD_UNKNOWN, indent);
+ break;
+ }
+
+ PGPUserAttributeSubpacketVector vector = attribute.getVector();
+
+ // generate and add new certificate
+ PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
+ masterPublicKey, vector);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
+ }
+ subProgressPop();
+
+ // 2c. Add revocations for revoked user ids
+ subProgressPush(32, 40);
for (int i = 0; i < saveParcel.mRevokeUserIds.size(); i++) {
progress(R.string.progress_modify_revokeuid, (i - 1) * (100 / saveParcel.mRevokeUserIds.size()));
@@ -540,7 +577,7 @@ public class PgpKeyOperation {
}
if (!exists) {
log.add(LogType.MSG_MF_ERROR_NOEXIST_REVOKE, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// a duplicate revocation will be removed during canonicalization, so no need to
@@ -571,7 +608,7 @@ public class PgpKeyOperation {
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
// foreign certificate?! error error error
log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// we know from canonicalization that if there is any revocation here, it
// is valid and not superseded by a newer certification.
@@ -592,7 +629,7 @@ public class PgpKeyOperation {
if (currentCert == null) {
// no certificate found?! error error error
log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// we definitely should not update certifications of revoked keys, so just leave it.
@@ -600,7 +637,7 @@ public class PgpKeyOperation {
// revoked user ids cannot be primary!
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
log.add(LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
continue;
}
@@ -649,7 +686,7 @@ public class PgpKeyOperation {
if (!ok) {
log.add(LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
}
@@ -665,7 +702,7 @@ public class PgpKeyOperation {
// Check if we were cancelled - again
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, indent);
- return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
}
// 4a. For each subkey change, generate new subkey binding certificate
@@ -681,7 +718,28 @@ public class PgpKeyOperation {
if (sKey == null) {
log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ if (change.mDummyStrip || change.mDummyDivert != null) {
+ // IT'S DANGEROUS~
+ // no really, it is. this operation irrevocably removes the private key data from the key
+ if (change.mDummyStrip) {
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ } else {
+ // the serial number must be 16 bytes in length
+ if (change.mDummyDivert.length != 16) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ }
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+ }
+
+ // This doesn't concern us any further
+ if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
+ continue;
}
// expiry must not be in the past
@@ -689,7 +747,7 @@ public class PgpKeyOperation {
new Date(change.mExpiry*1000).before(new Date())) {
log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// if this is the master key, update uid certificates instead
@@ -699,7 +757,7 @@ public class PgpKeyOperation {
if ((flags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
log.add(LogType.MSG_MF_ERROR_NO_CERTIFY, indent + 1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
PGPPublicKey pKey =
@@ -707,7 +765,7 @@ public class PgpKeyOperation {
flags, expiry, indent, log);
if (pKey == null) {
// error log entry has already been added by updateMasterCertificates itself
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
masterSecretKey = PGPSecretKey.replacePublicKey(sKey, pKey);
masterPublicKey = pKey;
@@ -762,7 +820,7 @@ public class PgpKeyOperation {
if (sKey == null) {
log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
indent+1, KeyFormattingUtils.convertKeyIdToHex(revocation));
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
PGPPublicKey pKey = sKey.getPublicKey();
@@ -774,30 +832,6 @@ public class PgpKeyOperation {
}
subProgressPop();
- // 4c. For each subkey to be stripped... do so
- subProgressPush(65, 70);
- for (int i = 0; i < saveParcel.mStripSubKeys.size(); i++) {
-
- progress(R.string.progress_modify_subkeystrip, (i-1) * (100 / saveParcel.mStripSubKeys.size()));
- long strip = saveParcel.mStripSubKeys.get(i);
- log.add(LogType.MSG_MF_SUBKEY_STRIP,
- indent, KeyFormattingUtils.convertKeyIdToHex(strip));
-
- PGPSecretKey sKey = sKR.getSecretKey(strip);
- if (sKey == null) {
- log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
- indent+1, KeyFormattingUtils.convertKeyIdToHex(strip));
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
- }
-
- // IT'S DANGEROUS~
- // no really, it is. this operation irrevocably removes the private key data from the key
- sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
-
- }
- subProgressPop();
-
// 5. Generate and add new subkeys
subProgressPush(70, 90);
for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {
@@ -805,7 +839,7 @@ public class PgpKeyOperation {
// Check if we were cancelled - again. This operation is expensive so we do it each loop.
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, indent);
- return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
}
progress(R.string.progress_modify_subkeyadd, (i-1) * (100 / saveParcel.mAddSubKeys.size()));
@@ -815,12 +849,12 @@ public class PgpKeyOperation {
if (add.mExpiry == null) {
log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (add.mExpiry > 0L && new Date(add.mExpiry*1000).before(new Date())) {
log.add(LogType.MSG_MF_ERROR_PAST_EXPIRY, indent +1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// generate a new secret key (privkey only for now)
@@ -832,7 +866,7 @@ public class PgpKeyOperation {
subProgressPop();
if (keyPair == null) {
log.add(LogType.MSG_MF_ERROR_PGP, indent +1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// add subkey binding signature (making this a sub rather than master key)
@@ -867,67 +901,20 @@ public class PgpKeyOperation {
// Check if we were cancelled - again. This operation is expensive so we do it each loop.
if (checkCancelled()) {
log.add(LogType.MSG_OPERATION_CANCELLED, indent);
- return new EditKeyResult(EditKeyResult.RESULT_CANCELLED, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
}
// 6. If requested, change passphrase
- if (saveParcel.mNewPassphrase != null) {
+ if (saveParcel.mNewUnlock != null) {
progress(R.string.progress_modify_passphrase, 90);
log.add(LogType.MSG_MF_PASSPHRASE, indent);
indent += 1;
- PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build()
- .get(SECRET_KEY_ENCRYPTOR_HASH_ALGO);
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
- Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
- // Build key encryptor based on new passphrase
- PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
- SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- saveParcel.mNewPassphrase.toCharArray());
-
- // noinspection unchecked
- for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
- log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent,
- KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));
-
- boolean ok = false;
-
- try {
- // try to set new passphrase
- sKey = PGPSecretKey.copyWithNewPassword(sKey, keyDecryptor, keyEncryptorNew);
- ok = true;
- } catch (PGPException e) {
-
- // if this is the master key, error!
- if (sKey.getKeyID() == masterPublicKey.getKeyID()) {
- log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
- }
-
- // being in here means decrypt failed, likely due to a bad passphrase try
- // again with an empty passphrase, maybe we can salvage this
- try {
- log.add(LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY, indent+1);
- PBESecretKeyDecryptor emptyDecryptor =
- new JcePBESecretKeyDecryptorBuilder().setProvider(
- Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
- sKey = PGPSecretKey.copyWithNewPassword(sKey, emptyDecryptor, keyEncryptorNew);
- ok = true;
- } catch (PGPException e2) {
- // non-fatal but not ok, handled below
- }
- }
-
- if (!ok) {
- // for a subkey, it's merely a warning
- log.add(LogType.MSG_MF_PASSPHRASE_FAIL, indent+1,
- KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));
- continue;
- }
-
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
-
+ sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
+ passphrase, saveParcel.mNewUnlock, log, indent);
+ if (sKR == null) {
+ // The error has been logged above, just return a bad state
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
indent -= 1;
@@ -936,20 +923,235 @@ public class PgpKeyOperation {
} catch (IOException e) {
Log.e(Constants.TAG, "encountered IOException while modifying key", e);
log.add(LogType.MSG_MF_ERROR_ENCODE, indent+1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} catch (PGPException e) {
Log.e(Constants.TAG, "encountered pgp error while modifying key", e);
log.add(LogType.MSG_MF_ERROR_PGP, indent+1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} catch (SignatureException e) {
Log.e(Constants.TAG, "encountered SignatureException while modifying key", e);
log.add(LogType.MSG_MF_ERROR_SIG, indent+1);
- return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
progress(R.string.progress_done, 100);
log.add(LogType.MSG_MF_SUCCESS, indent);
- return new EditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
+ return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
+
+ }
+
+ /** This method does the actual modifications in a keyring just like internal, except it
+ * supports only the subset of operations which require no passphrase, and will error
+ * otherwise.
+ */
+ private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
+ OperationLog log) {
+
+ int indent = 1;
+
+ progress(R.string.progress_modify, 0);
+
+ // Make sure the saveParcel includes only operations available without passphrae!
+ if (!saveParcel.isRestrictedOnly()) {
+ log.add(LogType.MSG_MF_ERROR_RESTRICTED, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ // Check if we were cancelled
+ if (checkCancelled()) {
+ log.add(LogType.MSG_OPERATION_CANCELLED, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
+ }
+
+ // The only operation we can do here:
+ // 4a. Strip secret keys, or change their protection mode (stripped/divert-to-card)
+ subProgressPush(50, 60);
+ for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
+
+ progress(R.string.progress_modify_subkeychange, (i - 1) * (100 / saveParcel.mChangeSubKeys.size()));
+ SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i);
+ log.add(LogType.MSG_MF_SUBKEY_CHANGE,
+ indent, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+
+ PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
+ if (sKey == null) {
+ log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ if (change.mDummyStrip || change.mDummyDivert != null) {
+ // IT'S DANGEROUS~
+ // no really, it is. this operation irrevocably removes the private key data from the key
+ if (change.mDummyStrip) {
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ } else {
+ // the serial number must be 16 bytes in length
+ if (change.mDummyDivert.length != 16) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
+ }
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+ }
+
+ }
+
+ // And we're done!
+ progress(R.string.progress_done, 100);
+ log.add(LogType.MSG_MF_SUCCESS, indent);
+ return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
+
+ }
+
+
+ private static PGPSecretKeyRing applyNewUnlock(
+ PGPSecretKeyRing sKR,
+ PGPPublicKey masterPublicKey,
+ PGPPrivateKey masterPrivateKey,
+ String passphrase,
+ ChangeUnlockParcel newUnlock,
+ OperationLog log, int indent) throws PGPException {
+
+ if (newUnlock.mNewPassphrase != null) {
+ sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent);
+
+ // if there is any old packet with notation data
+ if (hasNotationData(sKR)) {
+
+ log.add(LogType.MSG_MF_NOTATION_EMPTY, indent);
+
+ // add packet with EMPTY notation data (updates old one, but will be stripped later)
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+ { // set subpackets
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ hashedPacketsGen.setExportable(false, false);
+ sGen.setHashedSubpackets(hashedPacketsGen.generate());
+ }
+ sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
+ PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
+
+ masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR,
+ PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
+ }
+
+ return sKR;
+ }
+
+ if (newUnlock.mNewPin != null) {
+ sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent);
+
+ log.add(LogType.MSG_MF_NOTATION_PIN, indent);
+
+ // add packet with "pin" notation data
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+ { // set subpackets
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ hashedPacketsGen.setExportable(false, false);
+ hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1");
+ sGen.setHashedSubpackets(hashedPacketsGen.generate());
+ }
+ sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
+ PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
+
+ masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR,
+ PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
+
+ return sKR;
+ }
+
+ throw new UnsupportedOperationException("PIN passphrases not yet implemented!");
+
+ }
+
+ /** This method returns true iff the provided keyring has a local direct key signature
+ * with notation data.
+ */
+ private static boolean hasNotationData(PGPSecretKeyRing sKR) {
+ // noinspection unchecked
+ Iterator<PGPSignature> sigs = sKR.getPublicKey().getKeySignatures();
+ while (sigs.hasNext()) {
+ WrappedSignature sig = new WrappedSignature(sigs.next());
+ if (sig.getSignatureType() == PGPSignature.DIRECT_KEY
+ && sig.isLocal() && !sig.getNotation().isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static PGPSecretKeyRing applyNewPassphrase(
+ PGPSecretKeyRing sKR,
+ PGPPublicKey masterPublicKey,
+ String passphrase,
+ String newPassphrase,
+ OperationLog log, int indent) throws PGPException {
+
+ PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build()
+ .get(SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
+ // Build key encryptor based on new passphrase
+ PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
+ SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ newPassphrase.toCharArray());
+
+ // noinspection unchecked
+ for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
+ log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent,
+ KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));
+
+ boolean ok = false;
+
+ try {
+ // try to set new passphrase
+ sKey = PGPSecretKey.copyWithNewPassword(sKey, keyDecryptor, keyEncryptorNew);
+ ok = true;
+ } catch (PGPException e) {
+
+ // if this is the master key, error!
+ if (sKey.getKeyID() == masterPublicKey.getKeyID()) {
+ log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1);
+ return null;
+ }
+
+ // being in here means decrypt failed, likely due to a bad passphrase try
+ // again with an empty passphrase, maybe we can salvage this
+ try {
+ log.add(LogType.MSG_MF_PASSPHRASE_EMPTY_RETRY, indent+1);
+ PBESecretKeyDecryptor emptyDecryptor =
+ new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
+ sKey = PGPSecretKey.copyWithNewPassword(sKey, emptyDecryptor, keyEncryptorNew);
+ ok = true;
+ } catch (PGPException e2) {
+ // non-fatal but not ok, handled below
+ }
+ }
+
+ if (!ok) {
+ // for a subkey, it's merely a warning
+ log.add(LogType.MSG_MF_PASSPHRASE_FAIL, indent+1,
+ KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));
+ continue;
+ }
+
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+
+ }
+
+ return sKR;
}
@@ -1072,6 +1274,26 @@ public class PgpKeyOperation {
return sGen.generateCertification(userId, pKey);
}
+ private static PGPSignature generateUserAttributeSignature(
+ PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
+ PGPUserAttributeSubpacketVector vector)
+ throws IOException, PGPException, SignatureException {
+ PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
+ masterPrivateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ {
+ /* critical subpackets: we consider those important for a modern pgp implementation */
+ hashedPacketsGen.setSignatureCreationTime(true, new Date());
+ }
+
+ sGen.setHashedSubpackets(hashedPacketsGen.generate());
+ sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+ return sGen.generateCertification(vector, pKey);
+ }
+
private static PGPSignature generateRevocationSignature(
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException {