diff options
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.java | 157 |
1 files changed, 110 insertions, 47 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 9a08290e4..8dbc2208c 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 @@ -166,16 +164,21 @@ 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 == null || saveParcel.mAddSubKeys.isEmpty()) { log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent); return null; } - SubkeyAdd add = saveParcel.addSubKeys.remove(0); + 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; + } + PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize); if (add.mAlgorithm == Constants.choice.algorithm.elgamal) { @@ -195,7 +198,7 @@ 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.e(Constants.TAG, "pgp error encoding key", e); @@ -257,11 +260,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 +297,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, @@ -314,27 +322,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,7 +355,7 @@ public class PgpKeyOperation { PGPSignature currentCert = null; // noinspection unchecked for (PGPSignature cert : new IterableIterator<PGPSignature>( - masterPublicKey.getSignaturesForID(userId))) { + modifiedPublicKey.getSignaturesForID(userId))) { // if it's not a self cert, never mind if (cert.getKeyID() != masterPublicKey.getKeyID()) { continue; @@ -373,7 +385,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 +395,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 +413,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 +445,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 +463,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,10 +520,10 @@ public class PgpKeyOperation { } // 5. Generate and add new subkeys - for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) { + for (SaveKeyringParcel.SubkeyAdd add : saveParcel.mAddSubKeys) { try { - if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) { + 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; } @@ -485,7 +537,7 @@ public class PgpKeyOperation { PGPPublicKey pKey = keyPair.getPublicKey(); PGPSignature cert = generateSubkeyBindingSignature( masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, - add.mFlags, add.mExpiry); + add.mFlags, add.mExpiry == null ? 0 : add.mExpiry); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); PGPSecretKey sKey; { @@ -513,7 +565,7 @@ public class PgpKeyOperation { } // 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 +575,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 +598,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 +610,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 +652,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 +664,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 +693,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 +710,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; + } + } |