new-edit: add logging to modifySecretKeyRing operation

This commit is contained in:
Vincent Breitmoser
2014-06-17 20:40:26 +02:00
parent 5c47143d64
commit 4bff50bffc
4 changed files with 164 additions and 78 deletions

View File

@@ -47,6 +47,9 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Primes; import org.sufficientlysecure.keychain.util.Primes;
@@ -98,12 +101,6 @@ public class PgpKeyOperation {
} }
} }
void updateProgress(int current, int total) {
if (mProgress != null) {
mProgress.setProgress(current, total);
}
}
/** Creates new secret key. */ /** Creates new secret key. */
private PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase, private PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws PgpGeneralMsgIdException { boolean isMasterKey) throws PgpGeneralMsgIdException {
@@ -182,7 +179,9 @@ public class PgpKeyOperation {
} }
/** This method introduces a list of modifications specified by a SaveKeyringParcel to a /** This method introduces a list of modifications specified by a SaveKeyringParcel to a
* PGPSecretKeyRing. * WrappedSecretKeyRing.
*
* This method relies on WrappedSecretKeyRing's canonicalization property!
* *
* Note that PGPPublicKeyRings can not be directly modified. Instead, the corresponding * Note that PGPPublicKeyRings can not be directly modified. Instead, the corresponding
* PGPSecretKeyRing must be modified and consequently consolidated with its public counterpart. * PGPSecretKeyRing must be modified and consequently consolidated with its public counterpart.
@@ -191,8 +190,7 @@ public class PgpKeyOperation {
* *
*/ */
public UncachedKeyRing modifySecretKeyRing(WrappedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, public UncachedKeyRing modifySecretKeyRing(WrappedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
String passphrase) String passphrase, OperationLog log, int indent) {
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
/* /*
* 1. Unlock private key * 1. Unlock private key
@@ -205,6 +203,8 @@ public class PgpKeyOperation {
* 6. If requested, change passphrase * 6. If requested, change passphrase
*/ */
log.add(LogLevel.START, LogType.MSG_MR, indent);
indent += 1;
updateProgress(R.string.progress_building_key, 0, 100); updateProgress(R.string.progress_building_key, 0, 100);
// We work on bouncycastle object level here // We work on bouncycastle object level here
@@ -213,10 +213,16 @@ public class PgpKeyOperation {
PGPSecretKey masterSecretKey = sKR.getSecretKey(); PGPSecretKey masterSecretKey = sKR.getSecretKey();
// 1. Unlock private key // 1. Unlock private key
log.add(LogLevel.DEBUG, LogType.MSG_MR_UNLOCK, indent);
PGPPrivateKey masterPrivateKey; { PGPPrivateKey masterPrivateKey; {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( try {
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
} catch (PGPException e) {
log.add(LogLevel.ERROR, LogType.MSG_MR_UNLOCK_ERROR, indent+1);
return null;
}
} }
if (!Arrays.equals(saveParcel.mFingerprint, sKR.getPublicKey().getFingerprint())) { if (!Arrays.equals(saveParcel.mFingerprint, sKR.getPublicKey().getFingerprint())) {
return null; return null;
@@ -224,12 +230,14 @@ public class PgpKeyOperation {
updateProgress(R.string.progress_certifying_master_key, 20, 100); updateProgress(R.string.progress_certifying_master_key, 20, 100);
{ // work on master secret key // work on master secret key
try {
PGPPublicKey modifiedPublicKey = masterPublicKey; PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids // 2a. Add certificates for new user ids
for (String userId : saveParcel.addUserIds) { for (String userId : saveParcel.addUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MR_UID_ADD, indent);
PGPSignature cert = generateUserIdSignature(masterPrivateKey, PGPSignature cert = generateUserIdSignature(masterPrivateKey,
masterPublicKey, userId, false); masterPublicKey, userId, false);
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
@@ -237,6 +245,7 @@ public class PgpKeyOperation {
// 2b. Add revocations for revoked user ids // 2b. Add revocations for revoked user ids
for (String userId : saveParcel.revokeUserIds) { for (String userId : saveParcel.revokeUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MR_UID_REVOKE, indent);
PGPSignature cert = generateRevocationSignature(masterPrivateKey, PGPSignature cert = generateRevocationSignature(masterPrivateKey,
masterPublicKey, userId); masterPublicKey, userId);
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
@@ -244,6 +253,7 @@ public class PgpKeyOperation {
// 3. If primary user id changed, generate new certificates for both old and new // 3. If primary user id changed, generate new certificates for both old and new
if (saveParcel.changePrimaryUserId != null) { if (saveParcel.changePrimaryUserId != null) {
log.add(LogLevel.INFO, LogType.MSG_MR_UID_PRIMARY, indent);
// todo // todo
} }
@@ -254,70 +264,105 @@ public class PgpKeyOperation {
sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey); sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
} }
}
// 4a. For each subkey change, generate new subkey binding certificate // 4a. For each subkey change, generate new subkey binding certificate
for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) { for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId); log.add(LogLevel.INFO, LogType.MSG_MR_SUBKEY_CHANGE,
if (sKey == null) { new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent);
return null; PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
} if (sKey == null) {
PGPPublicKey pKey = sKey.getPublicKey(); log.add(LogLevel.ERROR, LogType.MSG_MR_SUBKEY_MISSING,
new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent + 1);
// generate and add new signature return null;
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, }
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
// 4b. For each subkey change, generate new subkey binding certificate
for (long revocation : saveParcel.revokeSubKeys) {
PGPSecretKey sKey = sKR.getSecretKey(revocation);
if (sKey == null) {
return null;
}
PGPPublicKey pKey = sKey.getPublicKey();
// generate and add new signature
PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey);
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
// 5. Generate and add new subkeys
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) {
try {
PGPSecretKey sKey = createKey(add.mAlgorithm, add.mKeysize, passphrase, false);
PGPPublicKey pKey = sKey.getPublicKey(); PGPPublicKey pKey = sKey.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, add.mFlags, add.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, cert); if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) {
sKey = PGPSecretKey.replacePublicKey(sKey, pKey); log.add(LogLevel.ERROR, LogType.MSG_MR_SUBKEY_PAST_EXPIRY,
new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent + 1);
return null;
}
// generate and add new signature
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
} catch (PgpGeneralMsgIdException e) {
return null;
} }
// 4b. For each subkey revocation, generate new subkey revocation certificate
for (long revocation : saveParcel.revokeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MR_SUBKEY_REVOKE,
new String[] { PgpKeyHelper.convertKeyIdToHex(revocation) }, indent);
PGPSecretKey sKey = sKR.getSecretKey(revocation);
if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MR_SUBKEY_MISSING,
new String[] { PgpKeyHelper.convertKeyIdToHex(revocation) }, indent+1);
return null;
}
PGPPublicKey pKey = sKey.getPublicKey();
// generate and add new signature
PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey);
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
// 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_MR_SUBKEY_PAST_EXPIRY, indent +1);
return null;
}
log.add(LogLevel.INFO, LogType.MSG_MR_SUBKEY_NEW, indent);
PGPSecretKey sKey = createKey(add.mAlgorithm, add.mKeysize, passphrase, false);
log.add(LogLevel.DEBUG, LogType.MSG_MR_SUBKEY_NEW_ID,
new String[] { PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()) }, indent+1);
PGPPublicKey pKey = sKey.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, add.mFlags, add.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, cert);
sKey = PGPSecretKey.replacePublicKey(sKey, pKey);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
} catch (PgpGeneralMsgIdException e) {
return null;
}
}
// 6. If requested, change passphrase
if (saveParcel.newPassphrase != null) {
log.add(LogLevel.INFO, LogType.MSG_MR_PASSPHRASE, indent);
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
.get(HashAlgorithmTags.SHA1);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.newPassphrase.toCharArray());
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
}
// This one must only be thrown by
} catch (IOException e) {
log.add(LogLevel.ERROR, LogType.MSG_MR_ERROR_ENCODE, indent+1);
return null;
} catch (PGPException e) {
log.add(LogLevel.ERROR, LogType.MSG_MR_ERROR_PGP, indent+1);
return null;
} catch (SignatureException e) {
log.add(LogLevel.ERROR, LogType.MSG_MR_ERROR_SIG, indent+1);
return null;
} }
// 6. If requested, change passphrase log.add(LogLevel.OK, LogType.MSG_MR_SUCCESS, indent);
if (saveParcel.newPassphrase != null) {
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
.get(HashAlgorithmTags.SHA1);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.newPassphrase.toCharArray());
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
}
return new UncachedKeyRing(sKR); return new UncachedKeyRing(sKR);
} }
@@ -377,7 +422,7 @@ public class PgpKeyOperation {
private static PGPSignature generateSubkeyBindingSignature( private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, 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 PgpGeneralMsgIdException, IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
// date for signing // date for signing
Date todayDate = new Date(); Date todayDate = new Date();
@@ -414,13 +459,10 @@ public class PgpKeyOperation {
if (expiry != null) { if (expiry != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")); Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(pKey.getCreationTime()); creationDate.setTime(pKey.getCreationTime());
// note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
// here we purposefully ignore partial days in each date - long type has // (Just making sure there's no programming error here, this MUST have been checked above!)
// no fractional part! if (new Date(expiry).before(todayDate)) {
long numDays = (expiry / 86400000) - throw new RuntimeException("Bad subkey creation date, this is a bug!");
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
} }
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis()); hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
} else { } else {

View File

@@ -52,6 +52,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -486,7 +487,9 @@ public class KeychainIntentService extends IntentService
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE); String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
WrappedSecretKeyRing secRing = providerHelper.getWrappedSecretKeyRing(masterKeyId); WrappedSecretKeyRing secRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
UncachedKeyRing ring = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); OperationLog log = new OperationLog();
UncachedKeyRing ring = keyOperations.modifySecretKeyRing(secRing, saveParcel,
passphrase, log, 0);
setProgress(R.string.progress_saving_key_ring, 90, 100); setProgress(R.string.progress_saving_key_ring, 90, 100);
providerHelper.saveSecretKeyRing(ring); providerHelper.saveSecretKeyRing(ring);
} catch (ProviderHelper.NotFoundException e) { } catch (ProviderHelper.NotFoundException e) {

View File

@@ -215,6 +215,24 @@ public class OperationResultParcel implements Parcelable {
MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert), MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert),
MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup), MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup),
MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old), MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old),
MSG_MR (R.string.msg_mr),
MSG_MR_ERROR_ENCODE (R.string.msg_mr_error_encode),
MSG_MR_ERROR_PGP (R.string.msg_mr_error_pgp),
MSG_MR_ERROR_SIG (R.string.msg_mr_error_sig),
MSG_MR_PASSPHRASE (R.string.msg_mr_passphrase),
MSG_MR_SUBKEY_CHANGE (R.string.msg_mr_subkey_change),
MSG_MR_SUBKEY_MISSING (R.string.msg_mr_subkey_missing),
MSG_MR_SUBKEY_NEW_ID (R.string.msg_mr_subkey_new_id),
MSG_MR_SUBKEY_NEW (R.string.msg_mr_subkey_new),
MSG_MR_SUBKEY_PAST_EXPIRY (R.string.msg_mr_subkey_past_expiry),
MSG_MR_SUBKEY_REVOKE (R.string.msg_mr_subkey_revoke),
MSG_MR_SUCCESS (R.string.msg_mr_success),
MSG_MR_UID_ADD (R.string.msg_mr_uid_add),
MSG_MR_UID_PRIMARY (R.string.msg_mr_uid_primary),
MSG_MR_UID_REVOKE (R.string.msg_mr_uid_revoke),
MSG_MR_UNLOCK_ERROR (R.string.msg_mr_unlock_error),
MSG_MR_UNLOCK (R.string.msg_mr_unlock),
; ;
private final int mMsgId; private final int mMsgId;
@@ -264,6 +282,10 @@ public class OperationResultParcel implements Parcelable {
add(new OperationResultParcel.LogEntryParcel(level, type, parameters, indent)); add(new OperationResultParcel.LogEntryParcel(level, type, parameters, indent));
} }
public void add(LogLevel level, LogType type, int indent) {
add(new OperationResultParcel.LogEntryParcel(level, type, null, indent));
}
public boolean containsWarnings() { public boolean containsWarnings() {
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) { for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) {
if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) { if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) {

View File

@@ -600,6 +600,25 @@
<string name="msg_kc_uid_revoke_old">Removing outdated revocation certificate for user id "%s"</string> <string name="msg_kc_uid_revoke_old">Removing outdated revocation certificate for user id "%s"</string>
<string name="msg_kc_uid_no_cert">No valid self-certificate found for user id %s, removing from ring</string> <string name="msg_kc_uid_no_cert">No valid self-certificate found for user id %s, removing from ring</string>
<!-- modifySecretKeyRing -->
<string name="msg_mr">Modifying keyring %s</string>
<string name="msg_mr_error_encode">Encoding exception!</string>
<string name="msg_mr_error_pgp">PGP internal exception!</string>
<string name="msg_mr_error_sig">Signature exception!</string>
<string name="msg_mr_passphrase">Changing passphrase</string>
<string name="msg_mr_subkey_change">Modifying subkey %s</string>
<string name="msg_mr_subkey_missing">Tried to operate on missing subkey %s!</string>
<string name="msg_mr_subkey_new">Generating new %1$s bit %2$s subkey</string>
<string name="msg_mr_subkey_new_id">New subkey id: %s</string>
<string name="msg_mr_subkey_past_expiry">Expiry date cannot be in the past!</string>
<string name="msg_mr_subkey_revoke">Revoking subkey %s</string>
<string name="msg_mr_success">Keyring successfully modified</string>
<string name="msg_mr_uid_add">Adding user id %s</string>
<string name="msg_mr_uid_primary">Changing primary uid to %s</string>
<string name="msg_mr_uid_revoke">Revoking user id %s</string>
<string name="msg_mr_unlock_error">Error unlocking keyring!</string>
<string name="msg_mr_unlock">Unlocking keyring</string>
<!-- unsorted --> <!-- unsorted -->
<string name="section_certifier_id">Certifier</string> <string name="section_certifier_id">Certifier</string>
<string name="section_cert">Certificate Details</string> <string name="section_cert">Certificate Details</string>