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.java157
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;
+ }
+
}