diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java index c3687d921..e8caa8c4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java @@ -48,7 +48,7 @@ import timber.log.Timber; */ public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 31; + private static final int DATABASE_VERSION = 32; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; private static KeychainDatabase sInstance; @@ -348,6 +348,20 @@ public class KeychainDatabase { case 30: // ignore. this case only came up in an unreleased beta. + + case 31: + addSubkeyValidFromField(db); + } + } + + private void addSubkeyValidFromField(SupportSQLiteDatabase db) { + try { + db.beginTransaction(); + db.execSQL("ALTER TABLE keys ADD COLUMN validFrom INTEGER NOT NULL DEFAULT 0;"); + db.execSQL("UPDATE keys SET validFrom = creation"); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java index 7e9ca053f..a9de18559 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java @@ -313,6 +313,11 @@ public class KeyRepository extends AbstractDao { return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()); } + public List getPublicEncryptionIds(long masterKeyId) { + SqlDelightQuery query = SubKey.FACTORY.selectEffectiveEncryptionKeyIdsByMasterKeyId(masterKeyId); + return mapAllRows(query, SubKey.FACTORY.selectEffectiveEncryptionKeyIdsByMasterKeyIdMapper()); + } + public static class NotFoundException extends Exception { public NotFoundException() { } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java index a19181113..483b82a83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java @@ -225,6 +225,7 @@ public class KeyWritableRepository extends KeyRepository { } Date creation = key.getCreationTime(); + Date bindingSignatureTime = key.getBindingSignatureTime(); Date expiry = key.getExpiryTime(); if (expiry != null) { if (key.isExpired()) { @@ -240,7 +241,7 @@ public class KeyWritableRepository extends KeyRepository { SubKey subKey = SubKey.create(masterKeyId, rank, key.getKeyId(), key.getBitStrength(), key.getCurveOid(), key.getAlgorithm(), key.getFingerprint(), - c, s, e, a, key.isRevoked(), SecretKeyType.UNAVAILABLE, key.isSecure(), creation, expiry); + c, s, e, a, key.isRevoked(), SecretKeyType.UNAVAILABLE, key.isSecure(), creation, expiry, bindingSignatureTime); operations.add(DatabaseBatchInteractor.createInsertSubKey(subKey)); ++rank; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 088166146..71eed7579 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -31,11 +31,13 @@ public abstract class SubKey implements KeysModel { public static SubKey create(long masterKeyId, long rank, long keyId, Integer keySize, String keyCurveOid, int algorithm, byte[] fingerprint, boolean canCertify, boolean canSign, boolean canEncrypt, boolean canAuth, - boolean isRevoked, SecretKeyType hasSecret, boolean isSecure, Date creation, Date expiry) { + boolean isRevoked, SecretKeyType hasSecret, boolean isSecure, Date creation, Date expiry, + Date validFrom) { long creationUnixTime = creation.getTime() / 1000; Long expiryUnixTime = expiry != null ? expiry.getTime() / 1000 : null; + long validFromTime = validFrom.getTime() / 1000; return new AutoValue_SubKey(masterKeyId, rank, keyId, keySize, keyCurveOid, algorithm, fingerprint, canCertify, - canSign, canEncrypt, canAuth, isRevoked, hasSecret, isSecure, creationUnixTime, expiryUnixTime); + canSign, canEncrypt, canAuth, isRevoked, hasSecret, isSecure, creationUnixTime, expiryUnixTime, validFromTime); } public static InsertKey createInsertStatement(SupportSQLiteDatabase db) { @@ -53,7 +55,7 @@ public abstract class SubKey implements KeysModel { public void bindTo(InsertKey statement) { statement.bind(master_key_id(), rank(), key_id(), key_size(), key_curve_oid(), algorithm(), fingerprint(), can_certify(), can_sign(), can_encrypt(), can_authenticate(), is_revoked(), has_secret(), is_secure(), - creation(), expiry()); + creation(), expiry(), validFrom()); } @AutoValue diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index c0e0eef32..c8650e9fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -455,7 +455,6 @@ public abstract class OperationResult implements Parcelable { MSG_KC_SUB_BAD_ERR(LogLevel.WARN, R.string.msg_kc_sub_bad_err), MSG_KC_SUB_BAD_LOCAL(LogLevel.WARN, R.string.msg_kc_sub_bad_local), MSG_KC_SUB_BAD_KEYID(LogLevel.WARN, R.string.msg_kc_sub_bad_keyid), - MSG_KC_SUB_BAD_TIME(LogLevel.WARN, R.string.msg_kc_sub_bad_time), MSG_KC_SUB_BAD_TIME_EARLY(LogLevel.WARN, R.string.msg_kc_sub_bad_time_early), MSG_KC_SUB_BAD_TYPE(LogLevel.WARN, R.string.msg_kc_sub_bad_type), MSG_KC_SUB_DUP (LogLevel.DEBUG, R.string.msg_kc_sub_dup), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java index 2ee4ab42d..045944833 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java @@ -134,11 +134,33 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { : PGPSignature.SUBKEY_REVOCATION).hasNext(); } - public boolean isExpired () { + public boolean isExpired() { Date expiry = getExpiryTime(); return expiry != null && expiry.before(new Date()); } + private boolean hasFutureSigningDate() { + if (isMasterKey()) { + return false; + } + + WrappedSignature subkeyBindingSignature = getSubkeyBindingSignature(); + return subkeyBindingSignature.getCreationTime().after(new Date()); + } + + private WrappedSignature getSubkeyBindingSignature() { + Iterator subkeyBindingSignatures = mPublicKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING); + PGPSignature singleSubkeyBindingsignature = (PGPSignature) subkeyBindingSignatures.next(); + if (subkeyBindingSignatures.hasNext()) { + throw new IllegalStateException(); + } + return new WrappedSignature(singleSubkeyBindingsignature); + } + + public Date getBindingSignatureTime() { + return isMasterKey() ? getCreationTime() : getSubkeyBindingSignature().getCreationTime(); + } + public boolean isSecure() { return PgpSecurityConstants.checkForSecurityProblems(this) == null; } @@ -206,7 +228,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey { /** Returns whether this key is valid, ie not expired or revoked. */ public boolean isValid() { - return !isRevoked() && !isExpired(); + return !isRevoked() && !isExpired() && !hasFutureSigningDate(); } // For use in key export only; returns the public key in a JCA compatible format. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 038cbf01a..32bb301dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -32,7 +32,7 @@ import java.io.UnsupportedEncodingException; import java.security.SignatureException; import java.util.Collection; import java.util.Date; -import java.util.Set; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; @@ -651,7 +651,7 @@ public class PgpSignEncryptOperation extends BaseOperation encryptSubKeyIds = keyRing.getEncryptIds(); + List encryptSubKeyIds = mKeyRepository.getPublicEncryptionIds(encryptMasterKeyId); for (Long subKeyId : encryptSubKeyIds) { CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId); cPk.addMethod(key.getPubKeyEncryptionGenerator(data.isHiddenRecipients())); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 2aa8bd05c..aea544be8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -912,13 +912,6 @@ public class UncachedKeyRing { continue; } - if (cert.getCreationTime().after(nowPlusOneDay)) { - // Creation date in the future? No way! - log.add(LogType.MSG_KC_SUB_BAD_TIME, indent); - badCerts += 1; - continue; - } - if (cert.getCreationTime().before(keyCreationTime)) { // Signature is earlier than key creation time log.add(LogType.MSG_KC_SUB_BAD_TIME_EARLY, indent); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index ec3627c06..51e229430 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -18,6 +18,7 @@ CREATE TABLE IF NOT EXISTS keys ( is_secure INTEGER AS Boolean NOT NULL, creation INTEGER NOT NULL, expiry INTEGER, + validFrom INTEGER NOT NULL, PRIMARY KEY(master_key_id, rank), FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE @@ -27,8 +28,8 @@ insertKey: INSERT INTO keys ( master_key_id, rank, key_id, key_size, key_curve_oid, algorithm, fingerprint, can_certify, can_sign, can_encrypt, can_authenticate, - is_revoked, has_secret, is_secure, creation, expiry - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + is_revoked, has_secret, is_secure, creation, expiry, validFrom + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); updateHasSecretByMasterKeyId: UPDATE keys @@ -91,7 +92,7 @@ SELECT master_key_id WHERE key_id = ?; selectSubkeysByMasterKeyId: -SELECT master_key_id, rank, key_id, key_size, key_curve_oid, algorithm, fingerprint, can_certify, can_sign, can_encrypt, can_authenticate, is_revoked, has_secret, is_secure, creation, expiry +SELECT master_key_id, rank, key_id, key_size, key_curve_oid, algorithm, fingerprint, can_certify, can_sign, can_encrypt, can_authenticate, is_revoked, has_secret, is_secure, creation, expiry, validFrom FROM keys WHERE master_key_id = ? ORDER BY rank ASC; @@ -106,6 +107,12 @@ SELECT fingerprint FROM keys WHERE key_id = ?; +selectEffectiveEncryptionKeyIdsByMasterKeyId: +SELECT key_id + FROM keys + WHERE is_revoked = 0 AND is_secure = 1 AND ( expiry IS NULL OR expiry >= strftime('%s', 'now') ) AND validFrom <= strftime('%s', 'now') + AND can_encrypt = 1 AND master_key_id = ?; + selectEffectiveSignKeyIdByMasterKeyId: SELECT key_id FROM keys