modifyKey: make SubkeyChange operations work

This commit is contained in:
Vincent Breitmoser 2014-07-11 15:20:16 +02:00
parent e7efd2c539
commit 7b195ac2e3
4 changed files with 64 additions and 27 deletions

View File

@ -65,10 +65,8 @@ import java.security.NoSuchProviderException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Iterator; 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 * This class is the single place where ALL operations that actually modify a PGP public or secret
@ -264,7 +262,7 @@ public class PgpKeyOperation {
// read masterKeyFlags, and use the same as before. // read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER // since this is the master key, this contains at least CERTIFY_OTHER
int masterKeyFlags = readMasterKeyFlags(masterSecretKey.getPublicKey()); int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER;
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log, indent); return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log, indent);
@ -450,6 +448,13 @@ public class PgpKeyOperation {
for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) { for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE, log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); 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); PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
if (sKey == null) { if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING, log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
@ -458,16 +463,39 @@ public class PgpKeyOperation {
} }
PGPPublicKey pKey = sKey.getPublicKey(); 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, log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId)); indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
return null; return null;
} }
// generate and add new signature. we can be sloppy here and just leave the old one, // keep old flags, or replace with new ones
// it will be removed during canonicalization 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, PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, change.mFlags, change.mExpiry, passphrase); sKey, pKey, flags, expiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig); pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
} }
@ -509,7 +537,7 @@ public class PgpKeyOperation {
PGPPublicKey pKey = keyPair.getPublicKey(); PGPPublicKey pKey = keyPair.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature( PGPSignature cert = generateSubkeyBindingSignature(
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
add.mFlags, add.mExpiry); add.mFlags, add.mExpiry == null ? 0 : add.mExpiry);
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
PGPSecretKey sKey; { PGPSecretKey sKey; {
@ -624,7 +652,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 IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder() PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
@ -636,7 +664,7 @@ public class PgpKeyOperation {
private static PGPSignature generateSubkeyBindingSignature( private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry) PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
// date for signing // date for signing
@ -665,17 +693,9 @@ public class PgpKeyOperation {
hashedPacketsGen.setKeyFlags(false, flags); hashedPacketsGen.setKeyFlags(false, flags);
} }
if (expiry != null) { if (expiry > 0) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC")); long creationTime = pKey.getCreationTime().getTime() / 1000;
creationDate.setTime(pKey.getCreationTime()); hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime);
// (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);
} }
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
@ -690,10 +710,16 @@ public class PgpKeyOperation {
} }
private static int readMasterKeyFlags(PGPPublicKey masterKey) { /** Returns all flags valid for this key.
int flags = KeyFlags.CERTIFY_OTHER; *
* 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 //noinspection unchecked
for(PGPSignature sig : new IterableIterator<PGPSignature>(masterKey.getSignatures())) { for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
if (!sig.hasSubpackets()) { if (!sig.hasSubpackets()) {
continue; continue;
} }

View File

@ -81,6 +81,10 @@ public class UncachedKeyRing {
return new UncachedPublicKey(mRing.getPublicKey()); return new UncachedPublicKey(mRing.getPublicKey());
} }
public UncachedPublicKey getPublicKey(long keyId) {
return new UncachedPublicKey(mRing.getPublicKey(keyId));
}
public Iterator<UncachedPublicKey> getPublicKeys() { public Iterator<UncachedPublicKey> getPublicKeys() {
final Iterator<PGPPublicKey> it = mRing.getPublicKeys(); final Iterator<PGPPublicKey> it = mRing.getPublicKeys();
return new Iterator<UncachedPublicKey>() { return new Iterator<UncachedPublicKey>() {

View File

@ -9,6 +9,7 @@ import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.ArrayList; import java.util.ArrayList;
@ -44,14 +45,19 @@ public class UncachedPublicKey {
} }
public Date getExpiryTime() { public Date getExpiryTime() {
Date creationDate = getCreationTime(); long seconds = mPublicKey.getValidSeconds();
if (mPublicKey.getValidDays() == 0) { if (seconds > Integer.MAX_VALUE) {
Log.e(Constants.TAG, "error, expiry time too large");
return null;
}
if (seconds == 0) {
// no expiry // no expiry
return null; return null;
} }
Date creationDate = getCreationTime();
Calendar calendar = GregorianCalendar.getInstance(); Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(creationDate); calendar.setTime(creationDate);
calendar.add(Calendar.DATE, mPublicKey.getValidDays()); calendar.add(Calendar.SECOND, (int) seconds);
return calendar.getTime(); return calendar.getTime();
} }

View File

@ -76,6 +76,7 @@ public class SaveKeyringParcel implements Parcelable {
public static class SubkeyChange implements Serializable { public static class SubkeyChange implements Serializable {
public long mKeyId; public long mKeyId;
public Integer mFlags; public Integer mFlags;
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
public Long mExpiry; public Long mExpiry;
public SubkeyChange(long keyId, Integer flags, Long expiry) { public SubkeyChange(long keyId, Integer flags, Long expiry) {
mKeyId = keyId; mKeyId = keyId;