From 0778bd8e475a01a666f0e5d3e4ce8de7aa386aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Fri, 18 Sep 2015 12:27:16 +0700 Subject: [PATCH 01/17] Initial web-of-trust implementation supporting GnuPG's trustdb.gpg. --- pgwot/build.gradle | 18 + .../org/bouncycastle/openpgp/wot/Config.java | 51 ++ .../bouncycastle/openpgp/wot/PgpKeyTrust.java | 45 ++ .../openpgp/wot/PgpUserIdTrust.java | 64 ++ .../bouncycastle/openpgp/wot/TrustConst.java | 40 ++ .../org/bouncycastle/openpgp/wot/TrustDb.java | 672 +++++++++++++++++ .../openpgp/wot/TrustDbException.java | 20 + .../bouncycastle/openpgp/wot/TrustDbIo.java | 676 ++++++++++++++++++ .../openpgp/wot/TrustDbIoException.java | 20 + .../bouncycastle/openpgp/wot/TrustModel.java | 63 ++ .../bouncycastle/openpgp/wot/TrustRecord.java | 478 +++++++++++++ .../openpgp/wot/TrustRecordType.java | 75 ++ .../org/bouncycastle/openpgp/wot/Util.java | 133 ++++ .../bouncycastle/openpgp/wot/key/PgpKey.java | 149 ++++ .../openpgp/wot/key/PgpKeyFingerprint.java | 97 +++ .../openpgp/wot/key/PgpKeyId.java | 86 +++ .../openpgp/wot/key/PgpKeyRegistry.java | 384 ++++++++++ .../openpgp/wot/key/PgpUserId.java | 53 ++ .../openpgp/wot/key/PgpUserIdNameHash.java | 142 ++++ .../openpgp/wot/package-info.java | 6 + .../openpgp/wot/AbstractTrustDbTest.java | 437 +++++++++++ .../wot/TrustDbProductiveFileTest.java | 182 +++++ .../openpgp/wot/UpdateTrustDbTest.java | 410 +++++++++++ pgwot/src/test/resources/log4j.properties | 6 + settings.gradle | 1 + 25 files changed, 4308 insertions(+) create mode 100644 pgwot/build.gradle create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java create mode 100644 pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java create mode 100644 pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java create mode 100644 pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java create mode 100644 pgwot/src/test/resources/log4j.properties diff --git a/pgwot/build.gradle b/pgwot/build.gradle new file mode 100644 index 0000000000..c1ca2475d2 --- /dev/null +++ b/pgwot/build.gradle @@ -0,0 +1,18 @@ +dependencies { + compile project(':pg') +} + +sourceCompatibility = JavaVersion.VERSION_1_7 +targetCompatibility = JavaVersion.VERSION_1_7 + +cobertura { + coverageDirs = [ + "${rootProject.projectDir}/pgwot/build/classes/main" + ] +} + +project.ext.logbackVersion = '1.0.13' + +dependencies { + testCompile 'org.assertj:assertj-core:1.5.0' +} \ No newline at end of file diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java new file mode 100644 index 0000000000..de54d2ef35 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java @@ -0,0 +1,51 @@ +package org.bouncycastle.openpgp.wot; + +public class Config implements TrustConst { + + public static final int TM_CLASSIC = 0; + public static final int TM_PGP = 1; + public static final int TM_EXTERNAL = 2; + public static final int TM_ALWAYS = 3; + public static final int TM_DIRECT = 4; + + private static final Config instance = new Config(); + + protected Config() { + } + + public static Config getInstance() { + return instance; + } + + public short getMarginalsNeeded() { + return 3; + } + + public short getCompletesNeeded() { + return 1; + } + + public short getMaxCertDepth() { + return 5; + } + + public short getTrustModel() { + return TM_PGP; // This must never be anything else! We support only TM_PGP = 1!!! + } + + public short getMinCertLevel() { + return 2; + } + + public String getTrustModelAsString() { + switch (getTrustModel()) { + case TM_CLASSIC: return "classic"; + case TM_PGP: return "PGP"; + case TM_EXTERNAL: return "external"; + case TM_ALWAYS: return "always"; + case TM_DIRECT: return "direct"; + default: + return "unknown[" + getTrustModel() + "]"; + } + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java new file mode 100644 index 0000000000..2fa3038862 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java @@ -0,0 +1,45 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; +import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; + +public class PgpKeyTrust { + + private final PgpKey pgpKey; + private final Map nameHash2UserIdTrust = new HashMap<>(); + + public PgpKeyTrust(final PgpKey pgpKey) { + this.pgpKey = assertNotNull("pgpKey", pgpKey); + } + + public PgpKey getPgpKey() { + return pgpKey; + } + + public PgpKeyFingerprint getPgpKeyFingerprint() { + return pgpKey.getPgpKeyFingerprint(); + } + + public PgpUserIdTrust getPgpUserIdTrust(final PgpUserId pgpUserId) { + assertNotNull("pgpUserId", pgpUserId); + PgpUserIdTrust pgpUserIdTrust = nameHash2UserIdTrust.get(pgpUserId.getNameHash()); + if (pgpUserIdTrust == null) { + pgpUserIdTrust = new PgpUserIdTrust(this, pgpUserId); + nameHash2UserIdTrust.put(pgpUserId.getNameHash(), pgpUserIdTrust); + } + return pgpUserIdTrust; + } + + public Collection getPgpUserIdTrusts() { + return Collections.unmodifiableCollection(nameHash2UserIdTrust.values()); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java new file mode 100644 index 0000000000..011ea241f5 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java @@ -0,0 +1,64 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import org.bouncycastle.openpgp.wot.key.PgpUserId; + +public class PgpUserIdTrust { + + private final PgpKeyTrust pgpKeyTrust; + private final PgpUserId pgpUserId; + + private int validity, ultimateCount, fullCount, marginalCount; + + public PgpUserIdTrust(final PgpKeyTrust pgpKeyTrust, final PgpUserId pgpUserId) { + this.pgpKeyTrust = assertNotNull("pgpKeyTrust", pgpKeyTrust); + this.pgpUserId = assertNotNull("pgpUserId", pgpUserId); + } + + public PgpKeyTrust getPgpKeyTrust() { + return pgpKeyTrust; + } + + public PgpUserId getPgpUserId() { + return pgpUserId; + } + + public int getValidity() { + return validity; + } + + public void setValidity(int validity) { + this.validity = validity; + } + + public int getUltimateCount() { + return ultimateCount; + } + public void setUltimateCount(int ultimateCount) { + this.ultimateCount = ultimateCount; + } + public void incUltimateCount() { + ++ultimateCount; + } + + public int getFullCount() { + return fullCount; + } + public void setFullCount(int fullCount) { + this.fullCount = fullCount; + } + public void incFullCount() { + ++fullCount; + } + + public int getMarginalCount() { + return marginalCount; + } + public void setMarginalCount(int marginalCount) { + this.marginalCount = marginalCount; + } + public void incMarginalCount() { + ++marginalCount; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java new file mode 100644 index 0000000000..830b228b37 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java @@ -0,0 +1,40 @@ +package org.bouncycastle.openpgp.wot; + +public interface TrustConst { + int TRUST_RECORD_LEN = 40; + int SIGS_PER_RECORD = (TRUST_RECORD_LEN - 10) / 5; + int ITEMS_PER_HTBL_RECORD = (TRUST_RECORD_LEN - 2) / 4; + int ITEMS_PER_HLST_RECORD = (TRUST_RECORD_LEN - 6) / 5; + int ITEMS_PER_PREF_RECORD = TRUST_RECORD_LEN - 10; + int MAX_LIST_SIGS_DEPTH = 20; + int MAX_CACHE_SIZE = 1024 * 1024; + + + + int TRUST_MASK = 15; + /** o: not yet calculated/assigned */ + int TRUST_UNKNOWN = 0; + /** e: calculation may be invalid */ + int TRUST_EXPIRED = 1; + /** q: not enough information for calculation */ + int TRUST_UNDEFINED = 2; + /** n: never trust this pubkey */ + int TRUST_NEVER = 3; + /** m: marginally trusted */ + int TRUST_MARGINAL = 4; + /** f: fully trusted */ + int TRUST_FULLY = 5; + /** u: ultimately trusted */ + int TRUST_ULTIMATE = 6; + + // BEGIN trust values not covered by the mask + /** r: revoked */ + int TRUST_FLAG_REVOKED = 32; + /** r: revoked but for subkeys */ + int TRUST_FLAG_SUB_REVOKED = 64; + /** d: key/uid disabled */ + int TRUST_FLAG_DISABLED = 128; + /** a check-trustdb is pending */ + int TRUST_FLAG_PENDING_CHECK = 256; + // END trust values not covered by the mask +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java new file mode 100644 index 0000000000..54623ee036 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java @@ -0,0 +1,672 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; +import org.bouncycastle.openpgp.wot.key.PgpKeyId; +import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; +import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * API for working with GnuPG's {@code trustdb.gpg}. + *

+ * An instance of this class is used for the following purposes: + *

    + *
  • Read the validity of a {@linkplain #getValidity(PGPPublicKey) certain key}, + * {@linkplain #getValidity(PGPPublicKey, PgpUserIdNameHash) user-identity or user-attribute}. + *
  • Find out whether a key is {@linkplain #isDisabled(PGPPublicKey) disabled}. + *
  • Mark a key {@linkplain #setDisabled(PGPPublicKey, boolean) disabled} or enabled. + *
  • Set a key's {@linkplain #setOwnerTrust(PGPPublicKey, int) owner-trust} attribute. + *
  • {@linkplain #updateTrustDb() Recalculate the web-of-trust}. + *
+ *

+ * This class was mostly ported from the GnuPG's {@code trustdb.h} and {@code trustdb.c} files. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ +public class TrustDb implements AutoCloseable, TrustConst { + private static final Logger logger = LoggerFactory.getLogger(TrustDb.class); + + private final TrustDbIo trustDbIo; + private final PgpKeyRegistry pgpKeyRegistry; + + private long startTime; + private long nextExpire; + private Map fingerprint2PgpKeyTrust; + private Set klist; + private Set fullTrust; + private DateFormat dateFormatIso8601WithTime; + + public TrustDb(final File file, final PgpKeyRegistry pgpKeyRegistry) { + assertNotNull("file", file); + this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); + this.trustDbIo = new TrustDbIo(file); + } + + @Override + public void close() throws Exception { + trustDbIo.close(); + } + + public DateFormat getDateFormatIso8601WithTime() { + if (dateFormatIso8601WithTime == null) + dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + return dateFormatIso8601WithTime; + } + + protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) { + PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); + if (pgpKeyTrust == null) { + pgpKeyTrust = new PgpKeyTrust(pgpKey); + fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); + } + return pgpKeyTrust; + } + + // reset_trust_records(void) + protected void resetTrustRecords() { + TrustRecord record; + long recordNum = 0; + int count = 0, nreset = 0; + + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) { + if (record.getType() == TrustRecordType.TRUST) { + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + ++count; + if (trust.getMinOwnerTrust() != 0) { + trust.setMinOwnerTrust((short) 0); + trustDbIo.putTrustRecord(record); + } + } + else if (record.getType() == TrustRecordType.VALID) { + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + if (((valid.getValidity() & TRUST_MASK) != 0) + || valid.getMarginalCount() != 0 + || valid.getFullCount() != 0) { + + valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); + valid.setMarginalCount((short) 0); + valid.setFullCount((short) 0); + nreset++; + trustDbIo.putTrustRecord(record); + } + } + } + + logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); + } + + /** + * Gets the assigned ownertrust value for the given public key. + * The key should be the master key. + */ + public int getOwnerTrust(final PGPPublicKey publicKey) { + assertNotNull("publicKey", publicKey); +// if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) +// return TRUST_UNKNOWN; // TODO maybe we should support other trust models... + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return TRUST_UNKNOWN; + + return trust.getOwnerTrust() & TRUST_MASK; + } + + /** + * Sets the given key's owner-trust. + *

+ * This value specifies how much the user trusts the owner of the given key in his function as notary + * certifying other keys. One of the following constants can be passed: + *

    + *
  • {@link TrustConst#TRUST_UNKNOWN TRUST_UNKNOWN} + *
  • {@link TrustConst#TRUST_NEVER TRUST_NEVER} + *
  • {@link TrustConst#TRUST_MARGINAL TRUST_MARGINAL} + *
  • {@link TrustConst#TRUST_FULLY TRUST_FULLY} + *
  • {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE} + *
+ *

+ * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. + * @param publicKey the key whose owner-trust is to be set. Must not be null. + * @param ownerTrust the owner-trust to be assigned. + */ + public void setOwnerTrust(final PGPPublicKey publicKey, final int ownerTrust) { + assertNotNull("publicKey", publicKey); + assertNonNegativeShort("ownerTrust", ownerTrust); + switch (ownerTrust) { + case TRUST_UNKNOWN: // 0 + case TRUST_NEVER: // 3 + case TRUST_MARGINAL: // 4 + case TRUST_FULLY: // 5 + case TRUST_ULTIMATE: // 6 + break; + default: + throw new IllegalArgumentException(String.format( + "ownerTrust=%d invalid! Must be one of: TRUST_UNKNOWN, TRUST_NEVER, TRUST_MARGINAL, TRUST_FULLY, TRUST_ULTIMATE", + ownerTrust)); + } + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; + + trust.setOwnerTrust((short) (ownerTrust | ownerTrustAdditionalFlags)); + trustDbIo.putTrustRecord(trust); + + markTrustDbStale(); + trustDbIo.flush(); + } + + protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); + return trust; + } + + public synchronized int getValidity(final PGPPublicKey publicKey) { + return getValidity(publicKey, (PgpUserIdNameHash) null); + } + + public synchronized int getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) { + assertNotNull("publicKey", publicKey); + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return TRUST_UNKNOWN; + + // Loop over all user IDs + long recordNum = trust.getValidList(); + int validity = 0; + while (recordNum != 0) { + TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + assertNotNull("valid", valid); + + if (pgpUserIdNameHash != null) { + // If a user ID is given we return the validity for that + // user ID ONLY. If the namehash is not found, then there + // is no validity at all (i.e. the user ID wasn't signed). + if (pgpUserIdNameHash.equals(valid.getNameHash())) { + validity = valid.getValidity(); + break; + } + } + else { + // If no user ID is given, we take the maximum validity over all user IDs + validity = Math.max(validity, valid.getValidity() & TRUST_MASK); + } + recordNum = valid.getNext(); + } + + if ( (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0 ) + validity |= TRUST_FLAG_DISABLED; + + if (publicKey.hasRevocation()) + validity |= TRUST_FLAG_REVOKED; + + if (isTrustDbStale()) + validity |= TRUST_FLAG_PENDING_CHECK; + + return validity; + } + + // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) + protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) { + assertNotNull("pgpUserId", pgpUserId); + assertNonNegativeShort("depth", depth); + assertNonNegativeShort("validity", validity); + assertNonNegativeShort("fullCount", fullCount); + assertNonNegativeShort("marginalCount", marginalCount); + + TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); + if (trust == null) { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); + trustDbIo.putTrustRecord(trust); + } + + TrustRecord.Valid valid = null; + + // locate an existing Valid record + final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); + long recordNum = trust.getValidList(); + while (recordNum != 0) { + valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) + break; + + recordNum = valid.getNext(); + } + + if (recordNum == 0) { // insert a new validity record + valid = new TrustRecord.Valid(); + valid.setNameHash(pgpUserIdNameHashBytes); + valid.setNext(trust.getValidList()); + trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record + trust.setValidList(valid.getRecordNum()); + } + + valid.setValidity((short) validity); + valid.setFullCount((short) fullCount); + valid.setMarginalCount((short) marginalCount); + trust.setDepth((short) depth); + trustDbIo.putTrustRecord(trust); + trustDbIo.putTrustRecord(valid); + } + + private static void assertNonNegativeShort(final String name, final int value) { + assertNotNull("name", name); + + if (value < 0) + throw new IllegalArgumentException(name + " < 0"); + + if (value > Short.MAX_VALUE) + throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); + } + + /** + * Marks all those keys that we have a secret key for as ultimately trusted. If we have a secret/private key, + * we assume it to be *our* key and we always trust ourselves. + */ + public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) { + for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) { + if (masterKey.getSecretKey() == null) + continue; + + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); + if (trust == null + || trust.getOwnerTrust() == TRUST_UNKNOWN + || !onlyIfMissing) { + + if (trust == null) { + trust = new TrustRecord.Trust(); + trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); + } + + trust.setDepth((short) 0); + trust.setOwnerTrust((short) TRUST_ULTIMATE); + trustDbIo.putTrustRecord(trust); + } + } + } + + protected Set getUltimatelyTrustedKeyFingerprints() { + Set result = new HashSet(); + TrustRecord record; + long recordNum = 0; + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) { + if (record.getType() == TrustRecordType.TRUST) { + TrustRecord.Trust trust = (TrustRecord.Trust) record; + if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) + result.add(new PgpKeyFingerprint(trust.getFingerprint())); + } + } + return result; + } + + public boolean isExpired(PGPPublicKey publicKey) { + assertNotNull("publicKey", publicKey); + + final Date creationTime = publicKey.getCreationTime(); + + final long validSeconds = publicKey.getValidSeconds(); + if (validSeconds != 0) { + long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); + return validUntilTimestamp < System.currentTimeMillis(); + } + return false; + // TODO there seem to be keys (very old keys) that seem to encode the validity differently. + // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it + // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. + // It's a very small number of keys only, hence I ignore it for now ;-) + } + + public boolean isDisabled(PGPPublicKey publicKey) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + return false; + + return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; + } + + public void setDisabled(PGPPublicKey publicKey, boolean disabled) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) { + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrust = trust.getOwnerTrust(); + if (disabled) + ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; + else + ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; + + trust.setOwnerTrust((short) ownerTrust); + + trustDbIo.putTrustRecord(trust); + trustDbIo.flush(); + } + + public synchronized boolean isTrustDbStale() { + final Config config = Config.getInstance(); + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + if (config.getTrustModel() != version.getTrustModel()) { + TrustModel configTrustModel; + try { + configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); + } catch (IllegalArgumentException x) { + configTrustModel = null; + } + + TrustModel versionTrustModel; + try { + versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); + } catch (IllegalArgumentException x) { + versionTrustModel = null; + } + + logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", + config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); + + return true; + } + + if (config.getCompletesNeeded() != version.getCompletesNeeded()) { + logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", + config.getCompletesNeeded(), version.getCompletesNeeded()); + + return true; + } + + if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) { + logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", + config.getMarginalsNeeded(), version.getMarginalsNeeded()); + + return true; + } + + if (config.getMaxCertDepth() != version.getCertDepth()) { + logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", + config.getMaxCertDepth(), version.getCertDepth()); + + return true; + } + + final Date now = new Date(); + if (version.getNextCheck().before(now)) { + logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", + getDateFormatIso8601WithTime().format(version.getNextCheck()), + getDateFormatIso8601WithTime().format(now)); + + return true; + } + + logger.trace("isTrustDbStale: stale=false"); + return false; + } + + /** + * Marks the trust-db as being stale. + *

+ * Either this method or {@link #updateTrustDb()} must be invoked whenever a new key was added + * to the key ring, because the WOT-related code does not keep track of key-ring-changes + * ({@link #isTrustDbStale()} does not detect them). + * @see #isTrustDbStale() + * @see #updateTrustDb() + */ + public synchronized void markTrustDbStale() { + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + version.setNextCheck(new Date(0)); + trustDbIo.putTrustRecord(version); + } + + /** + * Update the {@code trustdb.gpg} by recalculating all keys' validities, if it is needed. + * An update is needed, if the {@linkplain #isTrustDbStale() trust-db is stale}. + * @see #updateTrustDb() + * @see #isTrustDbStale() + */ + public synchronized void updateTrustDbIfNeeded() { + if (isTrustDbStale()) + updateTrustDb(); + } + + /** + * Update the {@code trustdb.gpg} by recalculating all keys' validities. + *

+ * Either this method or {@link #markTrustDbStale()} must be invoked whenever a new key was added + * to the key ring, because the WOT-related code does not keep track of key-ring-changes + * ({@link #isTrustDbStale()} does not detect them). + *

+ * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, + * because the implementation looked overly complicated. This method here is a re-implementation + * from scratch. It still seems to come very closely to the behaviour of GnuPG's original code. + * @see #updateTrustDbIfNeeded() + */ + public synchronized void updateTrustDb() { + final Config config = Config.getInstance(); + try { + fingerprint2PgpKeyTrust = new HashMap<>(); + fullTrust = new HashSet<>(); + + startTime = System.currentTimeMillis() / 1000; + nextExpire = Long.MAX_VALUE; + + resetTrustRecords(); + + final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); + if (ultimatelyTrustedKeyFingerprints.isEmpty()) { + logger.warn("updateTrustDb: There are no ultimately trusted keys!"); + return; + } + + // mark all UTKs as used and fully_trusted and set validity to ultimate + for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) { + final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); + if (utk == null) { + logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); + continue; + } + + fullTrust.add(utkFpr); + + for (PgpUserId pgpUserId : utk.getPgpUserIds()) + updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); + + final long expireDate = getExpireTimestamp(utk.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + klist = ultimatelyTrustedKeyFingerprints; + + for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) { + final List validatedKeys = validateKeyList(); + + klist = new HashSet<>(); + for (PgpKey pgpKey : validatedKeys) { + PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + klist.add(pgpKey.getPgpKeyFingerprint()); + + for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) { + final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); + + final int validity = pgpUserIdTrust.getValidity(); + updateValidity(pgpUserId, depth, validity, + pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); + + if (validity >= TRUST_FULLY) + fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); + } + + final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + logger.debug("updateTrustDb: depth={} keys={}", + depth, validatedKeys.size()); + } + + final Date nextExpireDate = new Date(nextExpire * 1000); + trustDbIo.updateVersionRecord(nextExpireDate); + + trustDbIo.flush(); + + logger.info("updateTrustDb: Next trust-db expiration date: {}", getDateFormatIso8601WithTime().format(nextExpireDate)); + } finally { + fingerprint2PgpKeyTrust = null; + klist = null; + fullTrust = null; + } + } + + private long getExpireTimestamp(PGPPublicKey pk) { + final long validSeconds = pk.getValidSeconds(); + if (validSeconds == 0) + return Long.MAX_VALUE; + + final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; + return result; + } + + /** + * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, + * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. + * @return the keys that were processed by this method. + */ + private List validateKeyList() { + final List result = new ArrayList<>(); + final Set signedPgpKeyFingerprints = new HashSet<>(); + for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) + signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); + + signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted + + for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) { + final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) { + logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); + continue; + } + result.add(pgpKey); + validateKey(pgpKey); + } + return result; + } + + /** + * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, + * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. + * @param pgpKey the pgp-key to be validated. Must not be null. + */ + private void validateKey(final PgpKey pgpKey) { + assertNotNull("pgpKey", pgpKey); + logger.debug("validateKey: {}", pgpKey); + + final Config config = Config.getInstance(); + final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + + final boolean expired = isExpired(pgpKey.getPublicKey()); +// final boolean disabled = isDisabled(pgpKey.getPublicKey()); + final boolean revoked = pgpKey.getPublicKey().hasRevocation(); + + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { + final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); + + pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 + pgpUserIdTrust.setUltimateCount(0); + pgpUserIdTrust.setFullCount(0); + pgpUserIdTrust.setMarginalCount(0); + + if (expired) + continue; + +// if (disabled) +// continue; + + if (revoked) + continue; + + for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) { + // It seems, the PGP trust model does not care about the certification level :-( + // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - + // there is no difference (at least according to my tests). + if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION + && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION + && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) + continue; + + final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); + if (signingKey == null) + continue; + + final int signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); + if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) + && signingOwnerTrust != TRUST_ULTIMATE) { + // It's *not* our own key [*not* TRUST_ULTIMATE] - hence we ignore the self-signature. + continue; + } + + int signingValidity = getValidity(signingKey.getPublicKey()) & TRUST_MASK; + if (signingValidity <= TRUST_MARGINAL) { + // If the signingKey is trusted only marginally or less, we ignore the certification completely. + // Only fully trusted keys are taken into account for transitive trust. + continue; + } + + // The owner-trust of the signing key is relevant. + switch (signingOwnerTrust) { + case TRUST_ULTIMATE: + pgpUserIdTrust.incUltimateCount(); + break; + case TRUST_FULLY: + pgpUserIdTrust.incFullCount(); + break; + case TRUST_MARGINAL: + pgpUserIdTrust.incMarginalCount(); + break; + default: // ignoring! + break; + } + } + + if (pgpUserIdTrust.getUltimateCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_MARGINAL); + } + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java new file mode 100644 index 0000000000..ac56d4f699 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java @@ -0,0 +1,20 @@ +package org.bouncycastle.openpgp.wot; + +public class TrustDbException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public TrustDbException() { + } + + public TrustDbException(String message) { + super(message); + } + + public TrustDbException(Throwable cause) { + super(cause); + } + + public TrustDbException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java new file mode 100644 index 0000000000..66e6ec044c --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java @@ -0,0 +1,676 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.wot.TrustRecord.HashLst; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * IO layer for GnuPG's {@code trustdb.gpg}. + *

+ * An instance of this class is used to read from and write to the {@code trustdb.gpg}, which is + * usually located in {@code ~/.gnupg/}. If this file does not exist (yet), it is created. + * The containing directory, however, is not created implicitly! + *

+ * Important: Do not use this class directly, if you don't have good reasons to! + * Instead, you should use the {@link TrustDb}. + *

+ * This class was mostly ported from the GnuPG's {@code tdbio.h} and {@code tdbio.c} files. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ +public class TrustDbIo implements AutoCloseable, TrustConst { + private static final Logger logger = LoggerFactory.getLogger(TrustDbIo.class); + + private final SortedMap dirtyRecordNum2TrustRecord = new TreeMap<>(); + private final LinkedHashSet cacheRecordNums = new LinkedHashSet(); + private final Map cacheRecordNum2TrustRecord = new HashMap<>(); + + private final File file; + private final RandomAccessFile raf; + private final FileLock fileLock; + private boolean closed; + + /** + * Create an instance of {@code TrustDbIo} with the given file (usually named {@code trustdb.gpg}). + * @param file the file to read from and write to. Must not be null. Is created, if + * not yet existing. + * @throws TrustDbIoException if reading from/writing to the {@code trustdb.gpg} failed. + */ + public TrustDbIo(final File file) throws TrustDbIoException { + this.file = assertNotNull("file", file); + try { + this.raf = new RandomAccessFile(file, "rw"); // or better use rwd/rws? maybe manually calling sync is sufficient?! + fileLock = raf.getChannel().lock(); + } catch (IOException x) { + throw new TrustDbIoException(x); + } + + if (getTrustRecord(0, TrustRecord.Version.class) == null) + createVersionRecord(); + } + + private void createVersionRecord() throws TrustDbIoException { + final Config config = Config.getInstance(); + + TrustRecord.Version version = new TrustRecord.Version(); + version.setVersion((short) 3); + version.setCreated(new Date()); + version.setNextCheck(version.getCreated()); // we should check it as soon as possible + version.setMarginalsNeeded(config.getMarginalsNeeded()); + version.setCompletesNeeded(config.getCompletesNeeded()); + version.setCertDepth(config.getMaxCertDepth()); + version.setTrustModel(config.getTrustModel()); // TODO maybe support other trust-models, too - currently only PGP is supported! + version.setMinCertLevel(config.getMinCertLevel()); + + version.setRecordNum(0); + putTrustRecord(version); + flush(); + } + + public synchronized void updateVersionRecord(final Date nextCheck) throws TrustDbIoException { + assertNotNull("nextCheck", nextCheck); + + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + Config config = Config.getInstance(); + + version.setCreated(new Date()); + version.setNextCheck(nextCheck); + version.setMarginalsNeeded(config.getMarginalsNeeded()); + version.setCompletesNeeded(config.getCompletesNeeded()); + version.setCertDepth(config.getMaxCertDepth()); + version.setTrustModel(config.getTrustModel()); + version.setMinCertLevel(config.getMinCertLevel()); + + putTrustRecord(version); + } + + public TrustRecord getTrustRecord(final long recordNum) throws TrustDbIoException { + return getTrustRecord(recordNum, TrustRecord.class); + } + + public TrustRecord.Trust getTrustByPublicKey(PGPPublicKey pk) throws TrustDbIoException + { + final byte[] fingerprint = pk.getFingerprint(); + return getTrustByFingerprint(fingerprint); + } + + /** Record number of the trust hashtable. */ + private long trustHashRec = 0; + + protected synchronized long getTrustHashRec() { + if (trustHashRec == 0) { + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + trustHashRec = version.getTrustHashTbl(); + if (trustHashRec == 0) { + createHashTable(0); + trustHashRec = version.getTrustHashTbl(); + } + } + return trustHashRec; + } + + /** + * Append a new empty hashtable to the trustdb. TYPE gives the type + * of the hash table. The only defined type is 0 for a trust hash. + * On return the hashtable has been created, written, the version + * record updated, and the data flushed to the disk. On a fatal error + * the function terminates the process. + */ + private void createHashTable(int type) throws TrustDbIoException { + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + flush(); // make sure, raf.length is correct. + + long offset; + long recnum; + + try { + offset = raf.length(); + raf.seek(offset); + } catch (IOException e) { + throw new TrustDbIoException(e); + } + + recnum = offset / TRUST_RECORD_LEN; + if (recnum <= 0) // This is will never be the first record. + throw new IllegalStateException("recnum <= 0"); + + if (type == 0) + version.setTrustHashTbl(recnum); + + // Now write the records making up the hash table. + final int n = (256 + ITEMS_PER_HTBL_RECORD - 1) / ITEMS_PER_HTBL_RECORD; + for (int i = 0; i < n; ++i, ++recnum) { + TrustRecord.HashTbl hashTable = new TrustRecord.HashTbl(); + hashTable.setRecordNum(recnum); + putTrustRecord(hashTable); + } + // Update the version record and flush. + putTrustRecord(version); + flush(); + } + + // ulong tdbio_new_recnum () + protected synchronized long newRecordNum() throws TrustDbIoException { + long recordNum; + + // Look for Free records. + final TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + if (version.getFirstFree() != 0) { + recordNum = version.getFirstFree(); + TrustRecord.Free free = getTrustRecord(recordNum, TrustRecord.Free.class); + assertNotNull("free", free); + + // Update dir record. + version.setFirstFree(free.getNext()); + putTrustRecord(version); + + // Zero out the new record. Means we convert from Free to Unused. + // => Done at the end! + } + else { // Not found - append a new record. + final long fileLength; + try { + fileLength = raf.length(); + } catch (IOException e) { + throw new TrustDbIoException(e); + } + recordNum = fileLength / TRUST_RECORD_LEN; + + if (recordNum < 1) // this is will never be the first record + throw new IllegalStateException("recnum < 1"); + + // Maybe our file-length is not up-to-date => consult the dirty records. + if (! dirtyRecordNum2TrustRecord.isEmpty()) { + long lastDirtyRecordNum = dirtyRecordNum2TrustRecord.lastKey(); + if (lastDirtyRecordNum >= recordNum) + recordNum = lastDirtyRecordNum + 1; + } + + // We must add a record, so that the next call to this function returns another recnum. + // => Done at the end! + } + + final TrustRecord.Unused unused = new TrustRecord.Unused(); + unused.setRecordNum(recordNum); + putTrustRecord(unused); + + return recordNum; + } + + public synchronized TrustRecord.Trust getTrustByFingerprint(final byte[] fingerprint) throws TrustDbIoException { + /* Locate the trust record using the hash table */ + TrustRecord rec = getTrustRecordViaHashTable(getTrustHashRec(), fingerprint, new TrustRecordMatcher() { + @Override + public boolean matches(final TrustRecord trustRecord) { + if (! (trustRecord instanceof TrustRecord.Trust)) + return false; + + final TrustRecord.Trust trust = (TrustRecord.Trust) trustRecord; + return Arrays.equals(trust.getFingerprint(), fingerprint); + } + }); + return (TrustRecord.Trust) rec; + } + + private static interface TrustRecordMatcher { + boolean matches(TrustRecord trustRecord); + } + + // static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) + public synchronized TrustRecord getTrustRecordViaHashTable(long table, byte[] key, TrustRecordMatcher matcher) { + long hashrec, item; + int msb; + int level = 0; + + hashrec = table; + next_level: while (true) { + msb = key[level] & 0xff; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); + //assertNotNull("hashTable", hashTable); + if (hashTable == null) + return null; // not found! + + item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); + if (item == 0) + return null; // not found! + + TrustRecord record = getTrustRecord(item); + assertNotNull("record", record); + + if (record.getType() == TrustRecordType.HTBL) { + hashrec = item; + if (++level >= key.length) + throw new TrustDbIoException("hashtable has invalid indirections"); + + continue next_level; + } + + if (record.getType() == TrustRecordType.HLST) { + TrustRecord.HashLst hashList = (TrustRecord.HashLst) record; + + for (;;) { + for (int i = 0; i < ITEMS_PER_HLST_RECORD; i++) { + if (hashList.getRNum(i) != 0) { + TrustRecord tmp = getTrustRecord(hashList.getRNum(i)); + if (tmp != null && matcher.matches(tmp)) + return tmp; + } + } + + if (hashList.getNext() != 0) { + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + assertNotNull("hashList", hashList); + } + else + return null; + } + } + + if (matcher.matches(record)) + return record; + else + return null; + } + } + + public synchronized T getTrustRecord(final long recordNum, Class expectedTrustRecordClass) throws TrustDbIoException { + assertNotNull("expectedTrustRecordClass", expectedTrustRecordClass); + final TrustRecordType expectedType = expectedTrustRecordClass == + TrustRecord.class ? null : TrustRecordType.fromClass(expectedTrustRecordClass); + + TrustRecord record = getFromCache(recordNum); + if (record == null) { + try { + raf.seek(recordNum * TRUST_RECORD_LEN); + } catch (IOException x) { + throw new TrustDbIoException(x); + } + + final byte[] buf = new byte[TRUST_RECORD_LEN]; + try { + raf.readFully(buf); + } catch (EOFException x) { + return null; + } catch (IOException x) { + throw new TrustDbIoException(x); + } + + int bufIdx = 0; + + final TrustRecordType type = TrustRecordType.fromId((short) (buf[bufIdx++] & 0xFF)); + if (expectedType != null && ! expectedType.equals(type)) + throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, type)); + + ++bufIdx; // Skip reserved byte. + + switch (type) { + case UNUSED: // unused (free) record + record = new TrustRecord.Unused(); + break; + case VERSION: // version record + final TrustRecord.Version version = new TrustRecord.Version(); + record = version; + + --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. + if (buf[bufIdx++] != 'g' + || buf[bufIdx++] != 'p' + || buf[bufIdx++] != 'g') + throw new TrustDbIoException(String.format("Not a trustdb file: %s", file.getAbsolutePath())); + + version.version = (short) (buf[bufIdx++] & 0xFF); + version.marginalsNeeded = (short) (buf[bufIdx++] & 0xFF); + version.completesNeeded = (short) (buf[bufIdx++] & 0xFF); + version.certDepth = (short) (buf[bufIdx++] & 0xFF); + version.trustModel = (short) (buf[bufIdx++] & 0xFF); + version.minCertLevel = (short) (buf[bufIdx++] & 0xFF); + + bufIdx += 2; // no idea why, but we have to skip 2 bytes + version.created = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); bufIdx += 4; + version.nextCheck = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + bufIdx += 4; // no idea why, but we have to skip 4 bytes + version.firstFree = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + version.trustHashTbl = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + + if (version.version != 3) + throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", version.version, file.getAbsolutePath())); + break; + case FREE: + final TrustRecord.Free free = new TrustRecord.Free(); + record = free; + free.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + break; + case HTBL: + final TrustRecord.HashTbl hashTbl = new TrustRecord.HashTbl(); + record = hashTbl; + for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) { + hashTbl.item[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + } + break; + case HLST: + final TrustRecord.HashLst hashLst = new TrustRecord.HashLst(); + record = hashLst; + hashLst.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { + hashLst.rnum[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + } + break; + case TRUST: + final TrustRecord.Trust trust = new TrustRecord.Trust(); + record = trust; + System.arraycopy(buf, bufIdx, trust.fingerprint, 0, 20); bufIdx += 20; + trust.ownerTrust = (short) (buf[bufIdx++] & 0xFF); + trust.depth = (short) (buf[bufIdx++] & 0xFF); + trust.minOwnerTrust = (short) (buf[bufIdx++] & 0xFF); + ++bufIdx; // no idea why, but we have to skip 1 byte + trust.validList = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + break; + case VALID: + final TrustRecord.Valid valid = new TrustRecord.Valid(); + record = valid; + System.arraycopy(buf, bufIdx, valid.nameHash, 0, 20); bufIdx += 20; + valid.validity = (short) (buf[bufIdx++] & 0xFF); + valid.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; + valid.fullCount = (short) (buf[bufIdx++] & 0xFF); + valid.marginalCount = (short) (buf[bufIdx++] & 0xFF); + break; + default: + throw new IllegalArgumentException("Unexpected TrustRecordType: " + type); + } + record.recordNum = recordNum; + putToCache(record); + } + else { + if (expectedType != null && ! expectedType.equals(record.getType())) + throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, record.getType())); + } + + return expectedTrustRecordClass.cast(record); + } + + public synchronized void putTrustRecord(final TrustRecord trustRecord) throws TrustDbIoException { + assertNotNull("trustRecord", trustRecord); + + if (trustRecord.getRecordNum() < 0) + trustRecord.setRecordNum(newRecordNum()); + + putToCache(trustRecord); + + final long recordNum = trustRecord.getRecordNum(); + dirtyRecordNum2TrustRecord.put(recordNum, trustRecord); + + if (trustRecord instanceof TrustRecord.Trust) + updateHashTable(getTrustHashRec(), ((TrustRecord.Trust) trustRecord).getFingerprint(), recordNum); + } + + protected synchronized void writeTrustRecord(final TrustRecord record) throws TrustDbIoException { + int bufIdx = 0; + final byte[] buf = new byte[TRUST_RECORD_LEN]; + + buf[bufIdx++] = (byte) record.getType().getId(); + ++bufIdx; // Skip reserved byte. + + switch (record.getType()) { + case UNUSED: // unused (free) record + break; + case VERSION: // version record + final TrustRecord.Version version = (TrustRecord.Version) record; + + --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. + buf[bufIdx++] = 'g'; + buf[bufIdx++] = 'p'; + buf[bufIdx++] = 'g'; + + buf[bufIdx++] = (byte) version.version; + buf[bufIdx++] = (byte) version.marginalsNeeded; + buf[bufIdx++] = (byte) version.completesNeeded; + buf[bufIdx++] = (byte) version.certDepth; + buf[bufIdx++] = (byte) version.trustModel; + buf[bufIdx++] = (byte) version.minCertLevel; + + bufIdx += 2; // no idea why, but we have to skip 2 bytes + + intToBytes((int) (version.created.getTime() / 1000L), buf, bufIdx); bufIdx += 4; + intToBytes((int) (version.nextCheck.getTime() / 1000L), buf, bufIdx); bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + bufIdx += 4; // no idea why, but we have to skip 4 bytes + intToBytes((int) version.firstFree, buf, bufIdx); bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + intToBytes((int) version.trustHashTbl, buf, bufIdx); bufIdx += 4; + + if (version.version != 3) + throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", version.version, file.getAbsolutePath())); + break; + case FREE: + final TrustRecord.Free free = (TrustRecord.Free) record; + intToBytes((int) free.next, buf, bufIdx); bufIdx += 4; + break; + case HTBL: + final TrustRecord.HashTbl hashTbl = (TrustRecord.HashTbl) record; + for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) { + intToBytes((int) hashTbl.item[i], buf, bufIdx); bufIdx += 4; + } + break; + case HLST: + final TrustRecord.HashLst hashLst = (TrustRecord.HashLst) record; + intToBytes((int) hashLst.next, buf, bufIdx); bufIdx += 4; + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { + intToBytes((int) hashLst.rnum[i], buf, bufIdx); bufIdx += 4; + } + break; + case TRUST: + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + System.arraycopy(trust.fingerprint, 0, buf, bufIdx, 20); bufIdx += 20; + buf[bufIdx++] = (byte) trust.ownerTrust; + buf[bufIdx++] = (byte) trust.depth; + buf[bufIdx++] = (byte) trust.minOwnerTrust; + ++bufIdx; // no idea why, but we have to skip 1 byte + intToBytes((int) trust.validList, buf, bufIdx); bufIdx += 4; + break; + case VALID: + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + System.arraycopy(valid.nameHash, 0, buf, bufIdx, 20); bufIdx += 20; + buf[bufIdx++] = (byte) valid.validity; + intToBytes((int) valid.next, buf, bufIdx); bufIdx += 4; + buf[bufIdx++] = (byte) valid.fullCount; + buf[bufIdx++] = (byte) valid.marginalCount; + break; + default: + throw new IllegalArgumentException("Unexpected TrustRecordType: " + record.getType()); + } + + try { + raf.seek(record.getRecordNum() * TRUST_RECORD_LEN); + raf.write(buf); + } catch (IOException e) { + throw new TrustDbIoException(e); + } + } + + /** + * Update a hashtable in the trustdb. TABLE gives the start of the + * table, KEY and KEYLEN are the key, NEWRECNUM is the record number + * to insert into the table. + * + * Return: 0 on success or an error code. + */ + // static int upd_hashtable (ulong table, byte *key, int keylen, ulong newrecnum) + protected synchronized void updateHashTable(long table, byte[] key, long recordNum) throws TrustDbIoException { +// TrustRecord lastrec, rec; + TrustRecord.HashTbl lastHashTable = null; + long hashrec, item; + int msb; + int level = 0; + + hashrec = table; + next_level: while (true) { + msb = key[level] & 0xff; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + + TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); + item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); + if (item == 0) { // Insert a new item into the hash table. + hashTable.setItem(msb % ITEMS_PER_HTBL_RECORD, recordNum); + putTrustRecord(hashTable); + return; + } + else if (item == recordNum) { // perfect match ;-) + return; + } + else { // Must do an update. + lastHashTable = hashTable; hashTable = null; + TrustRecord rec = getTrustRecord(item); + if (rec.getType() == TrustRecordType.HTBL) { + hashrec = item; + ++level; + if (level >= key.length) + throw new TrustDbIoException("hashtable has invalid indirections."); + + continue next_level; + } + else if (rec.getType() == TrustRecordType.HLST) { // Extend the list. + TrustRecord.HashLst hashList = (HashLst) rec; + // Check whether the key is already in this list. + for (;;) { + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { + if (hashList.getRNum(i) == recordNum) + return; // Okay, already in the list. + } + + if (hashList.getNext() == 0) + break; // key is not in the list + + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + assertNotNull("hashList", hashList); + } + + // The following line was added by me, Marco. I think the original GnuPG code missed this: We should start looking + // for a free entry in the *first* suitable HashList record again, because there might have been sth. dropped. + hashList = (HashLst) rec; + + // Find the next free entry and put it in. + for (;;) { + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { + if (hashList.getRNum(i) == 0) { + // Empty slot found. + hashList.setRnum(i, recordNum); + putTrustRecord(hashList); + return; // Done. + } + } + + if (hashList.getNext() != 0) { + // read the next reord of the list. + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + } + else { + // Append a new record to the list. + TrustRecord.HashLst old = hashList; + hashList = new TrustRecord.HashLst(); + hashList.setRnum(0, recordNum); + + putTrustRecord(hashList); // assigns the new recordNum, too + old.setNext(hashList.getRecordNum()); + putTrustRecord(old); + return; // Done. + } + } /* end loop over list slots */ + } + else { // Insert a list record. + if (rec.getType() != TrustRecordType.TRUST) + throw new IllegalStateException(String.format("hashtbl %d: %d/%d points to an invalid record %d", + table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item)); + + if (rec.getRecordNum() == recordNum) + return; // found - fine - no need to change anything ;-) + + TrustRecord.HashLst hashList = new TrustRecord.HashLst(); + hashList.setRnum(0, rec.getRecordNum()); // Old key record + hashList.setRnum(1, recordNum); // and new key record + putTrustRecord(hashList); + + // Update the hashtable record. + assertNotNull("lastHashTable", lastHashTable).setItem(msb % ITEMS_PER_HTBL_RECORD, hashList.getRecordNum()); + putTrustRecord(lastHashTable); + return; + } + } + } + } + + private TrustRecord getFromCache(final long recordNum) { + final TrustRecord trustRecord = cacheRecordNum2TrustRecord.get(recordNum); + logger.trace("getFromCache: recordNum={} found={}", recordNum, trustRecord != null); + return trustRecord; + } + + private void putToCache(TrustRecord trustRecord) { + assertNotNull("trustRecord", trustRecord); + final long recordNum = trustRecord.getRecordNum(); + + if (cacheRecordNum2TrustRecord.containsKey(recordNum)) + cacheRecordNums.remove(recordNum); + + while (cacheRecordNums.size() + 1 > MAX_CACHE_SIZE) { + final Long oldestRecordNum = cacheRecordNums.iterator().next(); + cacheRecordNums.remove(oldestRecordNum); + cacheRecordNum2TrustRecord.remove(oldestRecordNum); + } + + cacheRecordNum2TrustRecord.put(recordNum, trustRecord); + cacheRecordNums.add(recordNum); + } + + public synchronized void flush() throws TrustDbIoException { + for (TrustRecord trustRecord : dirtyRecordNum2TrustRecord.values()) + writeTrustRecord(trustRecord); + + dirtyRecordNum2TrustRecord.clear(); + + try { + raf.getFD().sync(); + } catch (IOException e) { + throw new TrustDbIoException(e); + } + } + + @Override + public synchronized void close() throws TrustDbIoException { + if (closed) + return; + + flush(); + closed = true; + try { + fileLock.release(); + raf.close(); + } catch (IOException e) { + throw new TrustDbIoException(e); + } + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java new file mode 100644 index 0000000000..3f6036e5c9 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java @@ -0,0 +1,20 @@ +package org.bouncycastle.openpgp.wot; + +public class TrustDbIoException extends TrustDbException { + private static final long serialVersionUID = 1L; + + public TrustDbIoException() { + } + + public TrustDbIoException(String message) { + super(message); + } + + public TrustDbIoException(Throwable cause) { + super(cause); + } + + public TrustDbIoException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java new file mode 100644 index 0000000000..76204279df --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java @@ -0,0 +1,63 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +public enum TrustModel { + + CLASSIC(0, "classic"), + PGP(1, "PGP"), + EXTERNAL(2, "external"), + ALWAYS(3, "always"), + DIRECT(4, "direct") + ; + + private final int numericId; + private final String stringId; + + private static TrustModel[] numericId2TrustModel; + + private TrustModel(int numericId, String stringId) { + this.numericId = numericId; + this.stringId = assertNotNull("stringId", stringId); + } + + public int getNumericId() { + return numericId; + } + + public String getStringId() { + return stringId; + } + + @Override + public String toString() { + return stringId; + } + + public static TrustModel fromNumericId(final int numericId) throws IllegalArgumentException { + if (numericId < 0 || numericId >= getNumericId2TrustModel().length) + throw new IllegalArgumentException("numericId unknown: " + numericId); + + final TrustModel trustModel = getNumericId2TrustModel()[numericId]; + if (trustModel == null) + throw new IllegalArgumentException("numericId unknown: " + numericId); + + return trustModel; + } + + private static TrustModel[] getNumericId2TrustModel() { + if (numericId2TrustModel == null) { + int maxNumericId = 0; + for (final TrustModel trustModel : values()) + maxNumericId = Math.max(maxNumericId, trustModel.getNumericId()); + + final TrustModel[] array = new TrustModel[maxNumericId + 1]; + for (final TrustModel trustModel : values()) + array[trustModel.getNumericId()] = trustModel; + + numericId2TrustModel = array; + } + return numericId2TrustModel; + } + +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java new file mode 100644 index 0000000000..75ee467695 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java @@ -0,0 +1,478 @@ +package org.bouncycastle.openpgp.wot; + +import java.util.Arrays; +import java.util.Date; + +public abstract class TrustRecord implements TrustConst { + + protected long recordNum = -1; + + public static class Unused extends TrustRecord { + @Override + public TrustRecordType getType() { + return TrustRecordType.UNUSED; + } + } + + public static class Version extends TrustRecord { + protected short version; // should be 3 + protected short marginalsNeeded; + protected short completesNeeded; + protected short certDepth; + protected short trustModel; + protected short minCertLevel; + protected Date created; // timestamp of trustdb creation + protected Date nextCheck; // timestamp of next scheduled check + protected long reserved; + protected long reserved2; + protected long firstFree; + protected long reserved3; + protected long trustHashTbl; + + @Override + public TrustRecordType getType() { + return TrustRecordType.VERSION; + } + + public short getVersion() { + return version; + } + public void setVersion(short version) { + this.version = version; + } + + public short getMarginalsNeeded() { + return marginalsNeeded; + } + public void setMarginalsNeeded(short marginals) { + this.marginalsNeeded = marginals; + } + + public short getCompletesNeeded() { + return completesNeeded; + } + public void setCompletesNeeded(short completes) { + this.completesNeeded = completes; + } + + public short getCertDepth() { + return certDepth; + } + public void setCertDepth(short certDepth) { + this.certDepth = certDepth; + } + + public short getTrustModel() { + return trustModel; + } + public void setTrustModel(short trustModel) { + this.trustModel = trustModel; + } + + public short getMinCertLevel() { + return minCertLevel; + } + public void setMinCertLevel(short minCertLevel) { + this.minCertLevel = minCertLevel; + } + + public Date getCreated() { + return created; + } + public void setCreated(Date created) { + this.created = created; + } + + public Date getNextCheck() { + return nextCheck; + } + public void setNextCheck(Date nextCheck) { + this.nextCheck = nextCheck; + } + + public long getReserved() { + return reserved; + } + + public long getReserved2() { + return reserved2; + } + + public long getFirstFree() { + return firstFree; + } + public void setFirstFree(long firstFree) { + this.firstFree = firstFree; + } + + public long getReserved3() { + return reserved3; + } + + public long getTrustHashTbl() { + return trustHashTbl; + } + public void setTrustHashTbl(long trustHashTbl) { + this.trustHashTbl = trustHashTbl; + } + +// @Override +// public Version clone() { +// Version clone = (Version) super.clone(); +// clone.created = (Date) created.clone(); +// clone.nextCheck = (Date) nextCheck.clone(); +// return clone; +// } + + @Override + public String toString() { + return String.format("%s[recordNum=%d version=%d marginalsNeeded=%d completesNeeded=%d certDepth=%d trustModel=%d minCertLevel=%d created=%s nextCheck=%s reserved=%d reserved2=%d firstFree=%d reserved3=%d trustHashTbl=%d]", + getClass().getSimpleName(), recordNum, version, marginalsNeeded, + completesNeeded, certDepth, trustModel, minCertLevel, + created, nextCheck, reserved, reserved2, firstFree, reserved3, trustHashTbl); + } + } + + public static class Free extends TrustRecord { + protected long next; + + public long getNext() { + return next; + } + public void setNext(long next) { + if (next < 0) + throw new IllegalArgumentException("next < 0"); + + this.next = next; + } + + @Override + public TrustRecordType getType() { + return TrustRecordType.FREE; + } + + @Override + public String toString() { + return String.format("%s[recordNum=%d next=%d]", + getClass().getSimpleName(), recordNum, next); + } + } + + public static class HashTbl extends TrustRecord { + protected long[] item = new long[ITEMS_PER_HTBL_RECORD]; + + public long getItem(int index) { + return item[index]; + } + public void setItem(int index, long value) { + if (value < 0) + throw new IllegalArgumentException("value < 0"); + + item[index] = value; + } + + @Override + public TrustRecordType getType() { + return TrustRecordType.HTBL; + } + +// @Override +// public HashTbl clone() { +// HashTbl clone = (HashTbl) super.clone(); +// clone.item = new long[item.length]; +// System.arraycopy(item, 0, clone.item, 0, item.length); +// return clone; +// } + + @Override + public String toString() { + return String.format("%s[recordNum=%d item=%s]", + getClass().getSimpleName(), recordNum, Arrays.toString(item)); + } + } + + public static class HashLst extends TrustRecord { + protected long next; + protected long[] rnum = new long[ITEMS_PER_HLST_RECORD]; // of another record + + @Override + public TrustRecordType getType() { + return TrustRecordType.HLST; + } + + public long getRNum(int index) { + return rnum[index]; + } + public void setRnum(int index, long value) { + if (value < 0) + throw new IllegalArgumentException("value < 0"); + + rnum[index] = value; + } + + public long getNext() { + return next; + } + public void setNext(long next) { + if (next < 0) + throw new IllegalArgumentException("next < 0"); + + this.next = next; + } + +// @Override +// public HashLst clone() { +// HashLst clone = (HashLst) super.clone(); +// clone.rnum = new long[rnum.length]; +// System.arraycopy(rnum, 0, clone.rnum, 0, rnum.length); +// return clone; +// } + + @Override + public String toString() { + return String.format("%s[recordNum=%d next=%d rnum=%s]", + getClass().getSimpleName(), recordNum, next, Arrays.toString(rnum)); + } + } + + public static class Trust extends TrustRecord { + protected byte[] fingerprint = new byte[20]; + protected short ownerTrust; + protected short depth; + protected long validList; + protected short minOwnerTrust; + + public Trust() { + } + + @Override + public TrustRecordType getType() { + return TrustRecordType.TRUST; + } + + public byte[] getFingerprint() { + return fingerprint; + } + public void setFingerprint(byte[] fingerprint) { + this.fingerprint = fingerprint; + } + + public short getOwnerTrust() { + return ownerTrust; + } + public void setOwnerTrust(short ownerTrust) { + this.ownerTrust = ownerTrust; + } + + public short getDepth() { + return depth; + } + public void setDepth(short depth) { + if (depth < 0) + throw new IllegalArgumentException("depth < 0"); + + this.depth = depth; + } + + public long getValidList() { + return validList; + } + public void setValidList(long validList) { + if (validList < 0) + throw new IllegalArgumentException("validList < 0"); + + this.validList = validList; + } + + public short getMinOwnerTrust() { + return minOwnerTrust; + } + public void setMinOwnerTrust(short minOwnerTrust) { + this.minOwnerTrust = minOwnerTrust; + } + +// @Override +// public Trust clone() { +// Trust clone = (Trust) super.clone(); +// clone.fingerprint = new byte[fingerprint.length]; +// System.arraycopy(fingerprint, 0, clone.fingerprint, 0, fingerprint.length); +// return clone; +// } + + @Override + public String toString() { + return String.format("%s[recordNum=%d fingerprint=%s ownerTrust=%d depth=%d validList=%d minOwnerTrust=%d]", + getClass().getSimpleName(), recordNum, encodeHexStr(fingerprint), + ownerTrust, depth, validList, minOwnerTrust); + } + } + + public static class Valid extends TrustRecord { + protected byte[] nameHash = new byte[20]; + protected long next; + protected short validity; + protected short fullCount; + protected short marginalCount; + + @Override + public TrustRecordType getType() { + return TrustRecordType.VALID; + } + + public byte[] getNameHash() { + return nameHash; + } + public void setNameHash(byte[] nameHash) { + this.nameHash = nameHash; + } + + public long getNext() { + return next; + } + public void setNext(long next) { + if (next < 0) + throw new IllegalArgumentException("next < 0"); + + this.next = next; + } + + public short getValidity() { + return validity; + } + + public void setValidity(short validity) { + this.validity = validity; + } + + public short getFullCount() { + return fullCount; + } + + public void setFullCount(short fullCount) { + this.fullCount = fullCount; + } + + public short getMarginalCount() { + return marginalCount; + } + + public void setMarginalCount(short marginalCount) { + this.marginalCount = marginalCount; + } + +// @Override +// public Valid clone() { +// Valid clone = (Valid) super.clone(); +// clone.nameHash = new byte[nameHash.length]; +// System.arraycopy(nameHash, 0, clone.nameHash, 0, nameHash.length); +// return clone; +// } + + @Override + public String toString() { + return String.format("%s[recordNum=%d nameHash=%s next=%d validity=%d fullCount=%d marginalCount=%d]", + getClass().getSimpleName(), recordNum, encodeHexStr(nameHash), + next, validity, fullCount, marginalCount); + } + }; + + public long getRecordNum() { + return recordNum; + } + + protected void setRecordNum(long recordNum) { + this.recordNum = recordNum; + } + + public abstract TrustRecordType getType(); + +// @Override +// public TrustRecord clone() { +// TrustRecord clone; +// try { +// clone = (TrustRecord) super.clone(); +// } catch (CloneNotSupportedException e) { +// throw new RuntimeException(e); +// } +// return clone; +// } + + // Copied from tdbio.c: + // struct trust_record { + // int rectype; + // int mark; + // int dirty; /* for now only used internal by functions */ + // struct trust_record *next; /* help pointer to build lists in memory */ + // ulong recnum; + // union { + // struct { /* version record: */ + // byte version; /* should be 3 */ + // byte marginalsNeeded; + // byte completesNeeded; + // byte cert_depth; + // byte trust_model; + // byte min_cert_level; + // ulong created; /* timestamp of trustdb creation */ + // ulong nextcheck; /* timestamp of next scheduled check */ + // ulong reserved; + // ulong reserved2; + // ulong firstfree; + // ulong reserved3; + // ulong trusthashtbl; + // } ver; + // struct { /* free record */ + // ulong next; + // } free; + // struct { + // ulong item[ITEMS_PER_HTBL_RECORD]; + // } htbl; + // struct { + // ulong next; + // ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */ + // } hlst; + // struct { + // byte fingerprint[20]; + // byte ownertrust; + // byte depth; + // ulong validlist; + // byte min_ownertrust; + // } trust; + // struct { + // byte namehash[20]; + // ulong next; + // byte validity; + // byte full_count; + // byte marginal_count; + // } valid; + // } r; + // }; + + public static String encodeHexStr(final byte[] buf) + { + return encodeHexStr(buf, 0, buf.length); + } + + /** + * Encode a byte array into a human readable hex string. For each byte, + * two hex digits are produced. They are concatenated without any separators. + * + * @param buf The byte array to translate into human readable text. + * @param pos The start position (0-based). + * @param len The number of bytes that shall be processed beginning at the position specified by pos. + * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. + * @see #encodeHexStr(byte[]) + * @see #decodeHexStr(String) + */ + public static String encodeHexStr(final byte[] buf, int pos, int len) + { + final StringBuilder hex = new StringBuilder(); + while (len-- > 0) { + final byte ch = buf[pos++]; + int d = (ch >> 4) & 0xf; + hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); + d = ch & 0xf; + hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); + } + return hex.toString(); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java new file mode 100644 index 0000000000..3f7da8adae --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java @@ -0,0 +1,75 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public enum TrustRecordType { + UNUSED((short) 0, TrustRecord.Unused.class), + VERSION((short) 1, TrustRecord.Version.class), + HTBL((short) 10, TrustRecord.HashTbl.class), + HLST((short) 11, TrustRecord.HashLst.class), + TRUST((short) 12, TrustRecord.Trust.class), + VALID((short) 13, TrustRecord.Valid.class), + FREE((short) 254, TrustRecord.Free.class); + + private static Map id2Type; + private static Map, TrustRecordType> class2Type; + + private final short id; + private Class trustRecordClass; + + private TrustRecordType(short id, Class trustRecordClass) { + this.id = id; + this.trustRecordClass = assertNotNull("trustRecordClass", trustRecordClass); + } + + public short getId() { + return id; + } + + public Class getTrustRecordClass() { + return trustRecordClass; + } + + public static TrustRecordType fromId(short id) { + TrustRecordType type = getId2Type().get(id); + if (type == null) + throw new IllegalArgumentException("id unknown: " + id); + + return type; + } + + public static TrustRecordType fromClass(Class trustRecordClass) { + assertNotNull("trustRecordClass", trustRecordClass); + TrustRecordType type = getClass2Type().get(trustRecordClass); + if (type == null) + throw new IllegalArgumentException("trustRecordClass unknown: " + trustRecordClass.getName()); + + return type; + } + + private static Map getId2Type() { + if (id2Type == null) { + Map m = new HashMap<>(values().length); + for (TrustRecordType type : values()) + m.put(type.getId(), type); + + id2Type = Collections.unmodifiableMap(m); + } + return id2Type; + } + + private static Map, TrustRecordType> getClass2Type() { + if (class2Type == null) { + Map, TrustRecordType> m = new HashMap<>(values().length); + for (TrustRecordType type : values()) + m.put(type.getTrustRecordClass(), type); + + class2Type = Collections.unmodifiableMap(m); + } + return class2Type; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java new file mode 100644 index 0000000000..74e465e91d --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java @@ -0,0 +1,133 @@ +package org.bouncycastle.openpgp.wot; + +public class Util { + + private Util() { + } + + public static byte[] longToBytes(final long value) { + final byte[] bytes = new byte[8]; + longToBytes(value, bytes, 0); + return bytes; + } + public static void longToBytes(final long value, final byte[] bytes, final int index) { + assertNotNull("bytes", bytes); + if (bytes.length - index < 8) + throw new IllegalArgumentException("bytes.length - index < 8"); + + for (int i = 0; i < 8; ++i) + bytes[index + i] = (byte) (value >>> (8 * (8 - 1 - i))); + } + + public static long bytesToLong(final byte[] bytes) { + assertNotNull("bytes", bytes); + if (bytes.length != 8) + throw new IllegalArgumentException("bytes.length != 8"); + + return bytesToLong(bytes, 0); + } + public static long bytesToLong(final byte[] bytes, final int index) { + assertNotNull("bytes", bytes); + if (bytes.length - index < 8) + throw new IllegalArgumentException("bytes.length - index < 8"); + + long value = 0; + for (int i = 0; i < 8; ++i) + value |= ((long) (bytes[index + i] & 0xff)) << (8 * (8 - 1 - i)); + + return value; + } + + public static byte[] intToBytes(final int value) { + final byte[] bytes = new byte[4]; + intToBytes(value, bytes, 0); + return bytes; + } + + public static void intToBytes(final int value, final byte[] bytes, final int index) { + assertNotNull("bytes", bytes); + if (bytes.length - index < 4) + throw new IllegalArgumentException("bytes.length - index < 4"); + + for (int i = 0; i < 4; ++i) + bytes[index + i] = (byte) (value >>> (8 * (4 - 1 - i))); + } + + public static int bytesToInt(final byte[] bytes) { + assertNotNull("bytes", bytes); + if (bytes.length != 4) + throw new IllegalArgumentException("bytes.length != 4"); + + return bytesToInt(bytes, 0); + } + + public static int bytesToInt(final byte[] bytes, final int index) { + assertNotNull("bytes", bytes); + if (bytes.length - index < 4) + throw new IllegalArgumentException("bytes.length - index < 4"); + + int value = 0; + for (int i = 0; i < 4; ++i) + value |= ((long) (bytes[index + i] & 0xff)) << (8 * (4 - 1 - i)); + + return value; + } + + public static String encodeHexStr(final byte[] buf) + { + return encodeHexStr(buf, 0, buf.length); + } + + /** + * Encode a byte array into a human readable hex string. For each byte, + * two hex digits are produced. They are concatenated without any separators. + * + * @param buf The byte array to translate into human readable text. + * @param pos The start position (0-based). + * @param len The number of bytes that shall be processed beginning at the position specified by pos. + * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. + * @see #encodeHexStr(byte[]) + * @see #decodeHexStr(String) + */ + public static String encodeHexStr(final byte[] buf, int pos, int len) + { + final StringBuilder hex = new StringBuilder(); + while (len-- > 0) { + final byte ch = buf[pos++]; + int d = (ch >> 4) & 0xf; + hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); + d = ch & 0xf; + hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); + } + return hex.toString(); + } + + /** + * Decode a string containing two hex digits for each byte. + * @param hex The hex encoded string + * @return The byte array represented by the given hex string + * @see #encodeHexStr(byte[]) + * @see #encodeHexStr(byte[], int, int) + */ + public static byte[] decodeHexStr(final String hex) + { + if (hex.length() % 2 != 0) + throw new IllegalArgumentException("The hex string must have an even number of characters!"); + + final byte[] res = new byte[hex.length() / 2]; + + int m = 0; + for (int i = 0; i < hex.length(); i += 2) { + res[m++] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); + } + + return res; + } + + public static final T assertNotNull(final String name, final T object) { + if (object == null) + throw new IllegalArgumentException(String.format("%s == null", name)); + + return object; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java new file mode 100644 index 0000000000..30f54e17e9 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java @@ -0,0 +1,149 @@ +package org.bouncycastle.openpgp.wot.key; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; + +public class PgpKey { + + private final PgpKeyId pgpKeyId; + + private final PgpKeyFingerprint pgpKeyFingerprint; + + private PGPPublicKeyRing publicKeyRing; + + private PGPSecretKeyRing secretKeyRing; + + private PGPPublicKey publicKey; + + private PGPSecretKey secretKey; + + private PgpKey masterKey; + + // A sub-key may be added twice, because we enlist from both the secret *and* public key ring + // collection. Therefore, we now use a LinkedHashSet (instead of an ArrayList). + private Set subKeyIds; + + private List subKeys; + + private List pgpUserIds; + + public PgpKey(final PgpKeyId pgpKeyId, final PgpKeyFingerprint pgpKeyFingerprint) { + this.pgpKeyId = assertNotNull("pgpKeyId", pgpKeyId); + this.pgpKeyFingerprint = assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); + } + + public PgpKeyId getPgpKeyId() { + return pgpKeyId; + } + + public PgpKeyFingerprint getPgpKeyFingerprint() { + return pgpKeyFingerprint; + } + + public PGPPublicKeyRing getPublicKeyRing() { + return publicKeyRing; + } + protected void setPublicKeyRing(PGPPublicKeyRing publicKeyRing) { + this.publicKeyRing = publicKeyRing; + } + + public PGPSecretKeyRing getSecretKeyRing() { + return secretKeyRing; + } + protected void setSecretKeyRing(PGPSecretKeyRing secretKeyRing) { + this.secretKeyRing = secretKeyRing; + } + + public PGPPublicKey getPublicKey() { + return publicKey; + } + protected void setPublicKey(final PGPPublicKey publicKey) { + this.publicKey = publicKey; + } + + public PGPSecretKey getSecretKey() { + return secretKey; + } + protected void setSecretKey(final PGPSecretKey secretKey) { + this.secretKey = secretKey; + } + + public List getPgpUserIds() { + if (pgpUserIds == null) { + final List l = new ArrayList<>(); + + for (final Iterator it = publicKey.getUserIDs(); it.hasNext(); ) { + final String userId = (String) it.next(); + l.add(new PgpUserId(this, userId)); + } + + for (final Iterator it = publicKey.getUserAttributes(); it.hasNext(); ) { + final PGPUserAttributeSubpacketVector userAttribute = (PGPUserAttributeSubpacketVector) it.next(); + l.add(new PgpUserId(this, userAttribute)); + } + pgpUserIds = Collections.unmodifiableList(l); + } + return pgpUserIds; + } + + /** + * Gets the master-key for this key. + * @return the master-key for this key. Always null, if this is a master-key. Never null, if + * this is a sub-key. + * @see #getSubKeyIds() + * @see #getSubKeys() + */ + public PgpKey getMasterKey() { + return masterKey; + } + protected void setMasterKey(PgpKey masterKey) { + this.masterKey = masterKey; + } + + public Set getSubKeyIds() { + if (subKeyIds == null && masterKey == null) // only a master-key can have sub-keys! hence we keep it null, if this is not a master-key! + subKeyIds = new LinkedHashSet<>(); + + return subKeyIds; + } + protected void setSubKeyIds(Set subKeyIds) { + this.subKeyIds = subKeyIds; + } + + /** + * Gets the sub-keys. + * @return the sub-keys. Never null, if this is a master-key. Always null, if this is a sub-key. + * @see #getMasterKey() + * @see #getSubKeyIds() + */ + public List getSubKeys() { + return subKeys; + } + protected void setSubKeys(List subKeys) { + this.subKeys = subKeys; + } + + @Override + public String toString() { + final Iterator userIdIt = publicKey.getUserIDs(); + final String primaryUserId; + if (userIdIt == null || ! userIdIt.hasNext()) + primaryUserId = null; + else + primaryUserId = (String) userIdIt.next(); + + return String.format("%s[pgpKeyId=%s masterKey=%s primaryUserId=%s]", this.getClass().getSimpleName(), pgpKeyId, masterKey, primaryUserId); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java new file mode 100644 index 0000000000..418eba0a17 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java @@ -0,0 +1,97 @@ +package org.bouncycastle.openpgp.wot.key; + +import static java.util.Arrays.*; +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.Arrays; + +public class PgpKeyFingerprint implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + private final byte[] fingerprint; + private transient int hashCode; + private transient WeakReference toString; + private transient WeakReference toHumanString; + + public PgpKeyFingerprint(final byte[] fingerprint) { + assertNotNull("fingerprint", fingerprint); + // In order to guarantee that this instance is immutable, we must copy the input. + this.fingerprint = copyOf(fingerprint, fingerprint.length); + } + + public PgpKeyFingerprint(final String fingerprint) { + assertNotNull("fingerprint", fingerprint); + this.fingerprint = decodeHexStr(fingerprint); + } + + public byte[] getBytes() { + // In order to guarantee that this instance stays immutable, we copy the byte array. + return copyOf(fingerprint, fingerprint.length); + } + + @Override + public int hashCode() { + if (hashCode == 0) + hashCode = Arrays.hashCode(fingerprint); + + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + + final PgpKeyFingerprint other = (PgpKeyFingerprint) obj; + return Arrays.equals(fingerprint, other.fingerprint); + } + + @Override + public int compareTo(PgpKeyFingerprint o) { + int res = Integer.compare(this.fingerprint.length, o.fingerprint.length); + if (res != 0) + return res; + + for (int i = 0; i < this.fingerprint.length; i++) { + res = Byte.compare(this.fingerprint[i], o.fingerprint[i]); + if (res != 0) + return res; + } + return 0; + } + + @Override + public String toString() { + String s = toString == null ? null : toString.get(); + if (s == null) { + s = encodeHexStr(fingerprint); + toString = new WeakReference(s); + } + return s; + } + + public String toHumanString() { + String s = toHumanString == null ? null : toHumanString.get(); + if (s == null) { + s = _toHumanString(); + toHumanString = new WeakReference(s); + } + return s; + } + + private String _toHumanString() { + final StringBuilder sb = new StringBuilder(); + final String string = toString(); + + for (int i = 0; i < string.length(); ++i) { + if (i > 0 && (i % 4 == 0)) + sb.append(' '); + + sb.append(string.charAt(i)); + } + return sb.toString(); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java new file mode 100644 index 0000000000..66c19786be --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java @@ -0,0 +1,86 @@ +package org.bouncycastle.openpgp.wot.key; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.Serializable; +import java.lang.ref.WeakReference; + +public class PgpKeyId implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + private final long pgpKeyId; + private transient WeakReference toString; + private transient WeakReference toHumanString; + + public PgpKeyId(final long pgpKeyId) { + this.pgpKeyId = pgpKeyId; + } + + public PgpKeyId(final String pgpKeyIdString) { + this(bytesToLong(decodeHexStr(assertNotNull("pgpKeyIdString", pgpKeyIdString)))); + } + + @Override + public String toString() { + String s = toString == null ? null : toString.get(); + if (s == null) { + s = encodeHexStr(longToBytes(pgpKeyId)); + toString = new WeakReference(s); + } + return s; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (pgpKeyId ^ (pgpKeyId >>> 32)); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final PgpKeyId other = (PgpKeyId) obj; + return this.pgpKeyId == other.pgpKeyId; + } + + @Override + public int compareTo(PgpKeyId other) { + assertNotNull("other", other); + // Same semantics as for normal numbers. + return (this.pgpKeyId < other.pgpKeyId ? -1 : + (this.pgpKeyId > other.pgpKeyId ? 1 : 0)); + } + + public long longValue() { + return pgpKeyId; + } + + public String toHumanString() { + String s = toHumanString == null ? null : toHumanString.get(); + if (s == null) { + s = _toHumanString(); + toHumanString = new WeakReference(s); + } + return s; + } + + private String _toHumanString() { + final StringBuilder sb = new StringBuilder(); + final String string = toString(); + + for (int i = 0; i < string.length(); ++i) { + if (i > 0 && (i % 4 == 0)) + sb.append(' '); + + sb.append(string.charAt(i)); + } + return sb.toString(); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java new file mode 100644 index 0000000000..74f34b1528 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -0,0 +1,384 @@ +package org.bouncycastle.openpgp.wot.key; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PgpKeyRegistry { + private static final Logger logger = LoggerFactory.getLogger(PgpKeyRegistry.class); + + private final File pubringFile; + private final File secringFile; + + private long pubringFileLastModified; + private long secringFileLastModified; + + private Map pgpKeyFingerprint2pgpKey; // all keys + private Map pgpKeyId2pgpKey; // all keys + private Map pgpKeyId2masterKey; // only master-keys + + private Map> signingKeyId2signedKeyIds; + + public PgpKeyRegistry(File pubringFile, File secringFile) { + this.pubringFile = assertNotNull("pubringFile", pubringFile); + this.secringFile = assertNotNull("secringFile", secringFile); + } + + public File getPubringFile() { + return pubringFile; + } + + public File getSecringFile() { + return secringFile; + } + + public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) { + final PgpKey pgpKey = getPgpKey(pgpKeyId); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); + + return pgpKey; + } + + public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) { + assertNotNull("pgpKeyId", pgpKeyId); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + return pgpKey; + } + + public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) { + final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); + + return pgpKey; + } + + public void markStale() { + pubringFileLastModified = 0; + secringFileLastModified = 0; + } + + public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) { + assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + return pgpKey; + } + + public synchronized Collection getMasterKeys() { + loadIfNeeded(); + return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); + } + + protected synchronized void loadIfNeeded() { + if (pgpKeyId2pgpKey == null + || getPubringFile().lastModified() != pubringFileLastModified + || getSecringFile().lastModified() != secringFileLastModified) { + logger.debug("loadIfNeeded: invoking load()."); + load(); + } + else + logger.trace("loadIfNeeded: *not* invoking load()."); + } + + protected synchronized void load() { + pgpKeyFingerprint2pgpKey = null; + final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); + final Map pgpKeyId2pgpKey = new HashMap<>(); + final Map pgpKeyId2masterKey = new HashMap<>(); + + final long pubringFileLastModified; + final long secringFileLastModified; + try { + final File secringFile = getSecringFile(); + logger.debug("load: secringFile='{}'", secringFile); + secringFileLastModified = secringFile.lastModified(); + if (secringFile.isFile()) { + final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) { + pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); + } + for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext(); ) { + final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext(); ) { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + + for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext(); ) { + final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); + final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + if (pgpKey == null) + throw new IllegalStateException("Secret key does not have corresponding public key in secret key ring! pgpKeyId=" + pgpKeyId); + + pgpKey.setSecretKey(secretKey); + logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); + } + } + } + + final File pubringFile = getPubringFile(); + logger.debug("load: pubringFile='{}'", pubringFile); + pubringFileLastModified = pubringFile.lastModified(); + if (pubringFile.isFile()) { + final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) { + pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); + } + + for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext(); ) { + final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext(); ) { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + } + } + } catch (IOException | PGPException x) { + throw new RuntimeException(x); + } + + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) { + if (pgpKey.getPublicKey() == null) + throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); + + if (pgpKey.getPublicKeyRing() == null) + throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); + } + + this.secringFileLastModified = secringFileLastModified; + this.pubringFileLastModified = pubringFileLastModified; + this.pgpKeyFingerprint2pgpKey = pgpKeyFingerprint2pgpKey; + this.pgpKeyId2pgpKey = pgpKeyId2pgpKey; + this.pgpKeyId2masterKey = pgpKeyId2masterKey; + + assignSubKeys(); + } + + private void assignSubKeys() { + for (final PgpKey masterKey : pgpKeyId2masterKey.values()) { + final Set subKeyIds = masterKey.getSubKeyIds(); + final List subKeys = new ArrayList(subKeyIds.size()); + for (final PgpKeyId subKeyId : subKeyIds) { + final PgpKey subKey = getPgpKeyOrFail(subKeyId); + subKeys.add(subKey); + } + masterKey.setSubKeys(Collections.unmodifiableList(subKeys)); + masterKey.setSubKeyIds(Collections.unmodifiableSet(subKeyIds)); + } + } + + private PgpKey enlistPublicKey(final Map pgpKeyFingerprint2pgpKey, + final Map pgpKeyId2PgpKey, + final Map pgpKeyId2masterKey, + PgpKey masterKey, final PGPKeyRing keyRing, final PGPPublicKey publicKey) + { + final PgpKeyId pgpKeyId = new PgpKeyId(publicKey.getKeyID()); + final PgpKeyFingerprint pgpKeyFingerprint = new PgpKeyFingerprint(publicKey.getFingerprint()); + + PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + if (pgpKey == null) { + pgpKey = new PgpKey(pgpKeyId, pgpKeyFingerprint); + pgpKeyFingerprint2pgpKey.put(pgpKeyFingerprint, pgpKey); + PgpKey old = pgpKeyId2PgpKey.put(pgpKeyId, pgpKey); + if (old != null) + throw new IllegalStateException(String.format("PGP-key-ID collision! Two keys with different fingerprints have the same key-ID! keyId=%s fingerprint1=%s fingerprint2=%s", + pgpKeyId, old.getPgpKeyFingerprint(), pgpKey.getPgpKeyFingerprint())); + } + + if (keyRing instanceof PGPSecretKeyRing) + pgpKey.setSecretKeyRing((PGPSecretKeyRing)keyRing); + else if (keyRing instanceof PGPPublicKeyRing) + pgpKey.setPublicKeyRing((PGPPublicKeyRing)keyRing); + else + throw new IllegalArgumentException("keyRing is neither an instance of PGPSecretKeyRing nor PGPPublicKeyRing!"); + + pgpKey.setPublicKey(publicKey); + + if (publicKey.isMasterKey()) { + masterKey = pgpKey; + pgpKeyId2masterKey.put(pgpKey.getPgpKeyId(), pgpKey); + } + else { + if (masterKey == null) + throw new IllegalStateException("First key is a non-master key!"); + + pgpKey.setMasterKey(masterKey); + masterKey.getSubKeyIds().add(pgpKey.getPgpKeyId()); + } + return masterKey; + } + + public synchronized Set getPgpKeyFingerprintsSignedBy(final PgpKeyFingerprint signingPgpKeyFingerprint) { + final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); + if (signingPgpKey == null) + return Collections.emptySet(); + + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); + if (pgpKeyIds == null) + return Collections.emptySet(); + + final Set result = new HashSet<>(pgpKeyIds.size()); + for (final PgpKeyId pgpKeyId : pgpKeyIds) { + final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); + result.add(pgpKey.getPgpKeyFingerprint()); + } + return Collections.unmodifiableSet(result); + } + + public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) { + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); + if (pgpKeyIds == null) + return Collections.emptySet(); + + return Collections.unmodifiableSet(pgpKeyIds); + } + + @SuppressWarnings("unchecked") + protected synchronized Map> getSigningKeyId2signedKeyIds() { + loadIfNeeded(); + if (signingKeyId2signedKeyIds == null) { + final Map> m = new HashMap<>(); + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) { + final PGPPublicKey publicKey = pgpKey.getPublicKey(); + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { + if (pgpUserId.getUserId() != null) { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext(); ) { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + else if (pgpUserId.getUserAttribute() != null) { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext(); ) { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + else + throw new IllegalStateException("WTF?!"); + } + + // It seems, there are both: certifications for individual user-ids and certifications for the + // entire key. I therefore first take the individual ones (above) into account then and then + // the ones for the entire key (below). + for (Iterator it = nullToEmpty(publicKey.getSignatures()); it.hasNext(); ) { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + signingKeyId2signedKeyIds = m; + } + return signingKeyId2signedKeyIds; + } + + @SuppressWarnings("unchecked") + public synchronized List getSignatures(final PgpUserId pgpUserId) { + assertNotNull("pgpUserId", pgpUserId); + final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); + + final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); + + final List result = new ArrayList<>(); + if (pgpUserId.getUserId() != null) { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext(); ) { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else if (pgpUserId.getUserAttribute() != null) { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext(); ) { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else + throw new IllegalStateException("WTF?!"); + +// // It seems, there are both: certifications for individual user-ids and certifications for the +// // entire key. I therefore first take the individual ones (above) into account then and then +// // the ones for the entire key (below). +// for (Iterator it = nullToEmpty(publicKey.getSignatures()); it.hasNext(); ) { +// final PGPSignature pgpSignature = (PGPSignature) it.next(); +// if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) { +// pgpSignatures.put(pgpSignature, pgpSignature); +// result.add(pgpSignature); +// } +// } + + return result; + } + + protected static Iterator nullToEmpty(final Iterator iterator) { + if (iterator == null) + return Collections.emptyList().iterator(); + else + return iterator; + } + + private void enlistInSigningKey2signedKeyIds(final Map> signingKeyId2signedKeyIds, + final PgpKey pgpKey, final PGPSignature pgpSignature) { + final PgpKeyId signingPgpKeyId = new PgpKeyId(pgpSignature.getKeyID()); + Set signedKeyIds = signingKeyId2signedKeyIds.get(signingPgpKeyId); + if (signedKeyIds == null) { + signedKeyIds = new HashSet<>(); + signingKeyId2signedKeyIds.put(signingPgpKeyId, signedKeyIds); + } + signedKeyIds.add(pgpKey.getPgpKeyId()); + } + + public boolean isCertification(PGPSignature pgpSignature) { + assertNotNull("pgpSignature", pgpSignature); + return isCertification(pgpSignature.getSignatureType()); + } + + public boolean isCertification(int pgpSignatureType) { + return PGPSignature.DEFAULT_CERTIFICATION == pgpSignatureType + || PGPSignature.NO_CERTIFICATION == pgpSignatureType + || PGPSignature.CASUAL_CERTIFICATION == pgpSignatureType + || PGPSignature.POSITIVE_CERTIFICATION == pgpSignatureType; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java new file mode 100644 index 0000000000..080432f72e --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java @@ -0,0 +1,53 @@ +package org.bouncycastle.openpgp.wot.key; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; + +public class PgpUserId { + private final PgpKey pgpKey; + private final String userId; + private final PGPUserAttributeSubpacketVector userAttribute; + private PgpUserIdNameHash nameHash; + + public PgpUserId(final PgpKey pgpKey, final String userId) { + this.pgpKey = assertNotNull("pgpKey", pgpKey); + this.userId = assertNotNull("userId", userId); + this.userAttribute = null; + } + + public PgpUserId(final PgpKey pgpKey, final PGPUserAttributeSubpacketVector userAttribute) { + this.pgpKey = assertNotNull("pgpKey", pgpKey); + this.userId = null; + this.userAttribute = assertNotNull("userAttribute", userAttribute); + } + + public PgpKey getPgpKey() { + return pgpKey; + } + + public String getUserId() { + return userId; + } + + public PGPUserAttributeSubpacketVector getUserAttribute() { + return userAttribute; + } + + // namehash_from_uid (PKT_user_id *uid) from keyid.c + public PgpUserIdNameHash getNameHash() { + if (nameHash == null) { + if (userId != null) + nameHash = PgpUserIdNameHash.createFromUserId(userId); + else + nameHash = PgpUserIdNameHash.createFromUserAttribute(userAttribute); + } + return nameHash; + } + + @Override + public String toString() { + return String.format("%s[pgpKeyId=%s userId=%s userAttribute=%s]", + this.getClass().getSimpleName(), getPgpKey().getPgpKeyId(), userId, userAttribute); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java new file mode 100644 index 0000000000..eec3f68d49 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java @@ -0,0 +1,142 @@ +package org.bouncycastle.openpgp.wot.key; + +import static java.util.Arrays.*; +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.bouncycastle.bcpg.UserAttributeSubpacket; +import org.bouncycastle.bcpg.UserAttributeSubpacketTags; +import org.bouncycastle.crypto.digests.RIPEMD160Digest; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; + +public class PgpUserIdNameHash implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + + private final byte[] namehash; + private transient int hashCode; + private transient WeakReference toString; + private transient WeakReference toHumanString; + + protected PgpUserIdNameHash(final byte[] namehash) { + assertNotNull("namehash", namehash); + this.namehash = namehash; + } + + public PgpUserIdNameHash(final String namehash) { + assertNotNull("namehash", namehash); + this.namehash = decodeHexStr(namehash); + } + + public byte[] getBytes() { + // In order to guarantee that this instance stays immutable, we copy the byte array. + return copyOf(namehash, namehash.length); + } + + @Override + public int hashCode() { + if (hashCode == 0) + hashCode = Arrays.hashCode(namehash); + + return hashCode; + } + + public boolean equals(final byte[] namehash) { + if (namehash == null) + return false; + + return Arrays.equals(this.namehash, namehash); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + + if (obj instanceof byte[]) + return equals((byte[]) obj); + + if (getClass() != obj.getClass()) return false; + + final PgpUserIdNameHash other = (PgpUserIdNameHash) obj; + return Arrays.equals(namehash, other.namehash); + } + + @Override + public int compareTo(PgpUserIdNameHash o) { + int res = Integer.compare(this.namehash.length, o.namehash.length); + if (res != 0) + return res; + + for (int i = 0; i < this.namehash.length; i++) { + res = Byte.compare(this.namehash[i], o.namehash[i]); + if (res != 0) + return res; + } + return 0; + } + + @Override + public String toString() { + String s = toString == null ? null : toString.get(); + if (s == null) { + s = encodeHexStr(namehash); + toString = new WeakReference(s); + } + return s; + } + + public String toHumanString() { + String s = toHumanString == null ? null : toHumanString.get(); + if (s == null) { + s = _toHumanString(); + toHumanString = new WeakReference(s); + } + return s; + } + + private String _toHumanString() { + final StringBuilder sb = new StringBuilder(); + final String string = toString(); + + for (int i = 0; i < string.length(); ++i) { + if (i > 0 && (i % 4 == 0)) + sb.append(' '); + + sb.append(string.charAt(i)); + } + return sb.toString(); + } + + public static PgpUserIdNameHash createFromUserId(final String userId) { + assertNotNull("userId", userId); + + final RIPEMD160Digest digest = new RIPEMD160Digest(); + byte[] userIdBytes = userId.getBytes(StandardCharsets.UTF_8); // TODO is this correct?! really UTF-8?! check with my own name! ;-) + digest.update(userIdBytes, 0, userIdBytes.length); + final byte[] out = new byte[digest.getDigestSize()]; + digest.doFinal(out, 0); + + return new PgpUserIdNameHash(out); + } + + public static PgpUserIdNameHash createFromUserAttribute(final PGPUserAttributeSubpacketVector userAttribute) { + assertNotNull("userAttribute", userAttribute); + + final RIPEMD160Digest digest = new RIPEMD160Digest(); + + // TODO this needs to be extended, if there is ever any other attribute possible, too! + // Currently, image seems to be the only supported attribute. Alternatively, we could get the data via reflection... + final UserAttributeSubpacket subpacket = userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE); + assertNotNull("subpacket", subpacket); + final byte[] data = assertNotNull("subpacket.data", subpacket.getData()); + digest.update(data, 0, data.length); + + final byte[] out = new byte[digest.getDigestSize()]; + digest.doFinal(out, 0); + return new PgpUserIdNameHash(out); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java new file mode 100644 index 0000000000..2bf5882815 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java @@ -0,0 +1,6 @@ +/** + * OpenPGP Web Of Trust (WOT) implementation. + *

+ * The most important class and entry point is the TrustDb. + */ +package org.bouncycastle.openpgp.wot; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java new file mode 100644 index 0000000000..2fe2978852 --- /dev/null +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java @@ -0,0 +1,437 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.Util.*; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Date; +import java.util.Iterator; + +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; +import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRingGenerator; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyId; +import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; +import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.junit.After; +import org.junit.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractTrustDbTest { + private static final Logger logger = LoggerFactory.getLogger(AbstractTrustDbTest.class); + + /** + * Skip cleaning up after a test run. This is useful to analyse the situation manually, after a test + * was run. + */ + protected static final boolean SKIP_CLEANUP = Boolean.parseBoolean(System.getProperty("SKIP_CLEANUP")); + + /** + * Skip additionally running 'gpg --check-trustdb --homedir ${tempGnuPgDir}' and comparing its results with + * the results of the Java code. + */ + protected static final boolean SKIP_GPG_CHECK_TRUST_DB = Boolean.parseBoolean(System.getProperty("SKIP_GPG_CHECK_TRUST_DB")); + + private final long validitySeconds = 365L * 24L * 3600L; + protected final SecureRandom secureRandom = new SecureRandom(); + + protected File tempDir; + protected File gnupgHomeDir; + + protected File pubringFile; + protected File secringFile; + protected File trustdbFile; + + protected PgpKeyRegistry pgpKeyRegistry; + + @Before + public void before() throws Exception { + File tempFile = File.createTempFile("abc-", ".tmp"); + tempDir = tempFile.getParentFile().getAbsoluteFile(); + tempFile.delete(); + initGnupgHomeDir(); + + pubringFile = new File(gnupgHomeDir, "pubring.gpg"); + secringFile = new File(gnupgHomeDir, "secring.gpg"); + trustdbFile = new File(gnupgHomeDir, "trustdb.gpg"); + + pgpKeyRegistry = new PgpKeyRegistry(pubringFile, secringFile); + } + + @After + public void after() throws Exception { + if (gnupgHomeDir != null) { + deleteGnupgHomeDir(); + gnupgHomeDir = null; + } + } + + protected void initGnupgHomeDir() { + gnupgHomeDir = new File(tempDir, "gnupg_" + Long.toHexString(System.currentTimeMillis()) + '_' + Integer.toHexString(Math.abs(secureRandom.nextInt()))); + gnupgHomeDir.mkdir(); + } + + protected void deleteGnupgHomeDir() { + if (SKIP_CLEANUP) + logger.warn("SKIP_CLEANUP is true => *NOT* deleting directory: {}", gnupgHomeDir); + else + deleteRecursively(gnupgHomeDir); + } + + protected void runGpgCheckTrustDb() throws IOException, InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder("gpg", "--check-trustdb", "--homedir", gnupgHomeDir.getPath()); + processBuilder.redirectErrorStream(true); + Process process = processBuilder.start(); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + final InputStream inputStream = process.getInputStream(); + new Thread() { + @Override + public void run() { + try { + final byte[] buf = new byte[1024 * 1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buf)) >= 0) { + output.write(buf, 0, bytesRead); + try { + logger.info("runGpg: {}", new String(buf, 0, bytesRead, StandardCharsets.UTF_8)); + } catch (Exception x) { + logger.warn("runGpg: Output could not be read into String: " + x, x); + } + } + } catch (Exception x) { + logger.error("runGpg: " + x, x); + } + } + }.start(); + + String outputString; + try { + outputString = new String(output.toByteArray()); + } catch (Exception x) { + outputString = null; + logger.warn("runGpg: Output could not be read into String: " + x, x); + } + + int processResult = process.waitFor(); + if (processResult != 0) + throw new IOException("gpg failed with error-code " + processResult + " and the following output:\n\n" + outputString); + } + + private static void deleteRecursively(File fileOrDir) { + assertNotNull("fileOrDir", fileOrDir); + fileOrDir.delete(); // first try to delete - if this is a symlink, this already succeeds + + File[] children = fileOrDir.listFiles(); + if (children != null) { + for (File child : children) + deleteRecursively(child); + } + + fileOrDir.delete(); // delete (maybe again) after the children are gone. + } + + protected PGPSecretKeyRingCollection readSecretKeyRingCollection() throws IOException, PGPException { + if (!secringFile.exists()) + secringFile.createNewFile(); + + try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) { + return new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator()); + } + } + + protected void writeSecretKeyRingCollection(PGPSecretKeyRingCollection collection) throws IOException, PGPException { + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(secringFile));) { + collection.encode(out); + } + } + + protected PGPPublicKeyRingCollection readPublicKeyRingCollection() throws IOException, PGPException { + if (!pubringFile.exists()) + pubringFile.createNewFile(); + + try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) { + return new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator()); + } + } + + protected void writePublicKeyRingCollection(PGPPublicKeyRingCollection collection) throws IOException, PGPException { + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pubringFile));) { + collection.encode(out); + } + } + + public PgpKey signPublicKey(PgpKey signingKey, int certificationType, PgpKey signedKey) throws IOException, PGPException { + assertNotNull("signingKey", signingKey); + assertNotNull("signedKey", signedKey); + + signedKey = pgpKeyRegistry.getPgpKey(signedKey.getPgpKeyId()); // maybe the given signedKey is stale! + + // null causes an exception - empty is possible, though + final char[] passphrase = new char[0]; + + if (signingKey.getMasterKey() != null) + signingKey = signingKey.getMasterKey(); // TODO should we maybe search for a separate signing-key? + + if (signedKey.getMasterKey() != null) + throw new IllegalArgumentException("signedKeyId does not reference a master-key! Cannot sign sub-keys!"); + +// final int masterKeyAlgorithm = PublicKeyAlgorithmTags.RSA_SIGN; + final int masterKeyAlgorithm = signingKey.getPublicKey().getAlgorithm(); + + PGPSecretKey secretKey = assertNotNull("signingKey.secretKey", signingKey.getSecretKey()); + PGPPrivateKey privateKey = extractPrivateKey(secretKey, passphrase); + + final BcPGPContentSignerBuilder signerBuilder = new BcPGPContentSignerBuilder(masterKeyAlgorithm, HashAlgorithmTags.SHA512); + final PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); + + sGen.init(certificationType, privateKey); + + final PGPSignatureSubpacketGenerator subpckGen = new PGPSignatureSubpacketGenerator(); + + // Using KeyFlags instead of PGPKeyFlags, because the latter seem incomplete. +// masterSubpckGen.setKeyFlags(false, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER | KeyFlags.AUTHENTICATION); +// masterSubpckGen.setPreferredSymmetricAlgorithms(false, preferredSymmetricAlgorithms); +// masterSubpckGen.setPreferredHashAlgorithms(false, preferredHashAlgorithms); +// masterSubpckGen.setPreferredCompressionAlgorithms(false, new int[] { CompressionAlgorithmTags.ZIP }); + subpckGen.setKeyExpirationTime(false, validitySeconds); + + sGen.setHashedSubpackets(subpckGen.generate()); + sGen.setUnhashedSubpackets(null); // AFAIK not needed + + PGPPublicKey signedPublicKey = signedKey.getPublicKey(); + for (PgpUserId pgpUserId : signedKey.getPgpUserIds()) { + String userId = pgpUserId.getUserId(); + if (userId == null) + throw new UnsupportedOperationException("Signing UserAttributes not yet supported!"); + + final PGPSignature certification = sGen.generateCertification(userId, signedPublicKey); + signedPublicKey = PGPPublicKey.addCertification(signedPublicKey, userId, certification); + } + + PGPPublicKeyRingCollection publicKeyRingCollection = readPublicKeyRingCollection(); + publicKeyRingCollection = PGPPublicKeyRingCollection.removePublicKeyRing(publicKeyRingCollection, signedKey.getPublicKeyRing()); + + PGPPublicKeyRing publicKeyRing = PGPPublicKeyRing.removePublicKey(signedKey.getPublicKeyRing(), signedKey.getPublicKey()); + publicKeyRing = PGPPublicKeyRing.insertPublicKey(publicKeyRing, signedPublicKey); + + publicKeyRingCollection = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRingCollection, publicKeyRing); + writePublicKeyRingCollection(publicKeyRingCollection); + + pgpKeyRegistry.markStale(); + return pgpKeyRegistry.getPgpKeyOrFail(signedKey.getPgpKeyId()); + } + + public PgpKey createPgpKey(final String userId) throws NoSuchAlgorithmException, IOException, PGPException { + assertNotNull("userId", userId); + + // null causes an exception - empty is possible, though + char[] passphrase = new char[0]; + + final Pair pair = createPGPSecretKeyRing(userId, passphrase); + final PGPPublicKeyRing publicKeyRing = pair.a; + final PGPSecretKeyRing secretKeyRing = pair.b; + + PGPSecretKeyRingCollection secretKeyRingCollection = readSecretKeyRingCollection(); + secretKeyRingCollection = PGPSecretKeyRingCollection.addSecretKeyRing(secretKeyRingCollection, secretKeyRing); + writeSecretKeyRingCollection(secretKeyRingCollection); + + PGPPublicKeyRingCollection publicKeyRingCollection = readPublicKeyRingCollection(); + publicKeyRingCollection = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRingCollection, publicKeyRing); + writePublicKeyRingCollection(publicKeyRingCollection); + + final PGPSecretKey secretKey = secretKeyRing.getSecretKey(); + + pgpKeyRegistry.markStale(); + return pgpKeyRegistry.getPgpKeyOrFail(new PgpKeyId(secretKey.getKeyID())); + } + + private static final class Pair { + public final A a; + public final B b; + + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + } + + private static PGPPrivateKey extractPrivateKey(final PGPSecretKey secretKey, final char[] passphrase) throws PGPException { + final PGPPrivateKey privateKey = secretKey.extractPrivateKey( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passphrase)); + + return privateKey; + } + + private Pair createPGPSecretKeyRing(final String userId, final char[] passphrase) throws PGPException, NoSuchAlgorithmException { + assertNotNull("userId", userId); + assertNotNull("passphrase", passphrase); + + logger.info("createPGPSecretKeyRing: Creating PGP key: userId='{}'", userId); + + final Date now = new Date(); + + final int masterKeyAlgorithm = PublicKeyAlgorithmTags.RSA_SIGN; + final int subKey1Algorithm = PublicKeyAlgorithmTags.RSA_ENCRYPT; + final int secretKeyEncryptionAlgorithm = SymmetricKeyAlgorithmTags.TWOFISH; + + final int[] preferredHashAlgorithms = new int[] { // TODO configurable?! + HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA1 + }; + + final int[] preferredSymmetricAlgorithms = new int[] { // TODO configurable?! + SymmetricKeyAlgorithmTags.TWOFISH, SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.BLOWFISH + }; + + logger.info("createPGPSecretKeyRing: Creating masterKeyPairGenerator..."); + final AsymmetricCipherKeyPairGenerator masterKeyPairGenerator = createAsymmetricCipherKeyPairGenerator(); + + logger.info("createPGPSecretKeyRing: Creating sub1KeyPairGenerator..."); + final AsymmetricCipherKeyPairGenerator sub1KeyPairGenerator = createAsymmetricCipherKeyPairGenerator(); + + + /* Create the master (signing-only) key. */ + logger.info("createPGPSecretKeyRing: Creating masterKeyPair..."); + final BcPGPKeyPair masterKeyPair = new BcPGPKeyPair(masterKeyAlgorithm, masterKeyPairGenerator.generateKeyPair(), now); + + final PGPSignatureSubpacketGenerator masterSubpckGen = new PGPSignatureSubpacketGenerator(); + + // Using KeyFlags instead of PGPKeyFlags, because the latter seem incomplete. + masterSubpckGen.setKeyFlags(false, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER | KeyFlags.AUTHENTICATION); + masterSubpckGen.setPreferredSymmetricAlgorithms(false, preferredSymmetricAlgorithms); + masterSubpckGen.setPreferredHashAlgorithms(false, preferredHashAlgorithms); + masterSubpckGen.setPreferredCompressionAlgorithms(false, new int[] { CompressionAlgorithmTags.ZIP }); + masterSubpckGen.setKeyExpirationTime(false, validitySeconds); + + + /* Create an encryption sub-key. */ + logger.info("createPGPSecretKeyRing: Creating sub1KeyPair..."); + final BcPGPKeyPair sub1KeyPair = new BcPGPKeyPair(subKey1Algorithm, sub1KeyPairGenerator.generateKeyPair(), now); + + final PGPSignatureSubpacketGenerator sub1SubpckGen = new PGPSignatureSubpacketGenerator(); + + sub1SubpckGen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE); + sub1SubpckGen.setPreferredSymmetricAlgorithms(false, preferredSymmetricAlgorithms); + sub1SubpckGen.setPreferredHashAlgorithms(false, preferredHashAlgorithms); + sub1SubpckGen.setPreferredCompressionAlgorithms(false, new int[] { CompressionAlgorithmTags.ZIP }); + sub1SubpckGen.setKeyExpirationTime(false, validitySeconds); + + + /* Create the key ring. */ + logger.info("createPGPSecretKeyRing: Creating keyRingGenerator..."); + final BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider(); + final BcPGPContentSignerBuilder signerBuilder = new BcPGPContentSignerBuilder(masterKeyAlgorithm, HashAlgorithmTags.SHA512); + final BcPBESecretKeyEncryptorBuilder pbeSecretKeyEncryptorBuilder = new BcPBESecretKeyEncryptorBuilder( + secretKeyEncryptionAlgorithm, digestCalculatorProvider.get(HashAlgorithmTags.SHA512)); + + // Tried SHA512 for checksumCalculator => org.bouncycastle.openpgp.PGPException: only SHA1 supported for key checksum calculations. + final PGPDigestCalculator checksumCalculator = digestCalculatorProvider.get(HashAlgorithmTags.SHA1); + + final PGPSignatureSubpacketVector hashedSubpackets = masterSubpckGen.generate(); + final PGPSignatureSubpacketVector unhashedSubpackets = null; + PGPKeyRingGenerator keyRingGenerator = new PGPKeyRingGenerator( + PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, + userId, + checksumCalculator, + hashedSubpackets, + unhashedSubpackets, + signerBuilder, + pbeSecretKeyEncryptorBuilder.build(passphrase)); + + + /* Add encryption subkey. */ + keyRingGenerator.addSubKey(sub1KeyPair, sub1SubpckGen.generate(), null); + + + /* Generate the key ring. */ + logger.info("createPGPSecretKeyRing: generateSecretKeyRing..."); + PGPSecretKeyRing secretKeyRing = keyRingGenerator.generateSecretKeyRing(); + + logger.info("createPGPSecretKeyRing: generatePublicKeyRing..."); + PGPPublicKeyRing publicKeyRing = keyRingGenerator.generatePublicKeyRing(); + + logger.info("createPGPSecretKeyRing: all done!"); + return new Pair<>(publicKeyRing, secretKeyRing); + } + + private static PGPPublicKey getMasterKeyOrFail(final PGPPublicKeyRing publicKeyRing) { + for (Iterator it = publicKeyRing.getPublicKeys(); it.hasNext(); ) { + PGPPublicKey pk = (PGPPublicKey) it.next(); + if (pk.isMasterKey()) { + return pk; + } + } + throw new IllegalStateException("No masterKey found!"); + } + + private AsymmetricCipherKeyPairGenerator createAsymmetricCipherKeyPairGenerator() throws NoSuchAlgorithmException { + AsymmetricCipherKeyPairGenerator keyPairGenerator = new RSAKeyPairGenerator(); + keyPairGenerator.init(createRsaKeyGenerationParameters()); + return keyPairGenerator; + } + + private RSAKeyGenerationParameters createRsaKeyGenerationParameters() { + /* + * This value should be a Fermat number. 0x10001 (F4) is current recommended value. 3 (F1) is known to be safe also. + * 3, 5, 17, 257, 65537, 4294967297, 18446744073709551617, + *

+ * Practically speaking, Windows does not tolerate public exponents which do not fit in a 32-bit unsigned integer. + * Using e=3 or e=65537 works "everywhere". + *

+ * See: stackoverflow: RSA Public exponent defaults to 65537. ... What are the impacts of my choices? + */ + final BigInteger publicExponent = BigInteger.valueOf(0x10001); + + /* + * How certain do we want to be that the chosen primes are really primes. + *

+ * The higher this number, the more tests are done to make sure they are primes (and not composites). + *

+ * See: What is the correct value for “certainty” in RSA key pair generation? + * and + * Does a high exponent compensate for a low degree of certainty? + */ + final int certainty = 12; + + return new RSAKeyGenerationParameters( + publicExponent, secureRandom, 1024, certainty); // IMPORTANT 1024 is TOO WEAK for productive use, but we want it to be fast for our tests! + } +} diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java new file mode 100644 index 0000000000..c46df6faef --- /dev/null +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java @@ -0,0 +1,182 @@ +package org.bouncycastle.openpgp.wot; + +import static org.assertj.core.api.Assertions.*; +import static org.bouncycastle.openpgp.wot.TrustConst.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bouncycastle.openpgp.wot.TrustRecord.Trust; +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyId; +import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Ignore("This test is for playing around while developing - it's not a regular test!") +public class TrustDbProductiveFileTest extends AbstractTrustDbTest { + private static final Logger logger = LoggerFactory.getLogger(TrustDbProductiveFileTest.class); + + @Override + protected void initGnupgHomeDir() { + String userHome = System.getProperty("user.home"); + gnupgHomeDir = new File(userHome, ".gnupg"); + } + + @Override + protected void deleteGnupgHomeDir() { + // nothing - MUST NOT delete our productive ".gnupg" directory! + } + + @Test + public void readMyProductiveTrustDb() throws Exception { + try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile);) { + long recordNum = -1; + TrustRecord trustRecord; + List trustFingerprints = new ArrayList<>(); + while ((trustRecord = trustDbIo.getTrustRecord(++recordNum)) != null) { + System.out.println(trustRecord); + if (trustRecord.getType() == TrustRecordType.TRUST) + trustFingerprints.add(((TrustRecord.Trust) trustRecord).getFingerprint()); + } + + for (byte[] trustFingerprint : trustFingerprints) { + Trust trust = trustDbIo.getTrustByFingerprint(trustFingerprint); + assertThat(trust).isNotNull(); + } + } + } + + @Test + public void updateMyProductiveDbHashTable() throws Exception { + try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile);) { + long recordNum = -1; + TrustRecord trustRecord; + List trusts = new ArrayList<>(); + while ((trustRecord = trustDbIo.getTrustRecord(++recordNum)) != null) { + if (trustRecord.getType() == TrustRecordType.TRUST) + trusts.add((TrustRecord.Trust) trustRecord); + } + + for (TrustRecord.Trust trust : trusts) { + trustDbIo.putTrustRecord(trust); + } + } + } + + @Test + public void readBrokenRecord() throws Exception { + byte[] fingerprint = new byte[] + { -5, 17, -44, -69, 123, 36, 70, 120, 51, 122, -83, -117, -57, -65, 38, -48, -69, 97, 120, 102 }; + + try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile);) { + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(fingerprint); + if (trust == null) { + long recordNum = -1; + TrustRecord trustRecord; + while ((trustRecord = trustDbIo.getTrustRecord(++recordNum)) != null) { + System.out.println(trustRecord); + if (trustRecord.getType() == TrustRecordType.TRUST) { + TrustRecord.Trust t = (TrustRecord.Trust) trustRecord; + if (Arrays.equals(fingerprint, t.getFingerprint())) + fail("Trust was not found via hashtable, but found by full-scan: recordNum = " + recordNum); + } + } + } + } + System.out.println("trust not found!!!"); + } + + @Test + public void isExpired() throws Exception { + PgpKey pgpKey = pgpKeyRegistry.getPgpKey(new PgpKeyId("56422A5E710E3371")); + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.isExpired(pgpKey.getPublicKey()); + } + } + + @Test + public void updateMyProductiveTrustDb() throws Exception { + runGpgCheckTrustDb(); + + Map pgpUserId2ValidityOriginal = getPgpUserId2Validity(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + } + + Map pgpUserId2ValidityMine = getPgpUserId2Validity(); + + for (PgpUserId pgpUserId : pgpUserId2ValidityOriginal.keySet()) { + if (! pgpUserId2ValidityMine.containsKey(pgpUserId)) + fail(String.format("pgpUserId2ValidityMine is missing pgpUserId=%s", pgpUserId)); + } + + for (PgpUserId pgpUserId : pgpUserId2ValidityMine.keySet()) { + if (! pgpUserId2ValidityOriginal.containsKey(pgpUserId)) + fail(String.format("pgpUserId2ValidityOriginal is missing pgpUserId=%s", pgpUserId)); + } + + int successValidityQty = 0; + int warningValidityQty = 0; + List fatals = new ArrayList<>(); + for (Map.Entry me : pgpUserId2ValidityOriginal.entrySet()) { + PgpUserId pgpUserId = me.getKey(); + Integer originalValidity = me.getValue(); + assertThat(originalValidity).isNotNull(); + + Integer myValidity = pgpUserId2ValidityMine.get(pgpUserId); + assertThat(myValidity).isNotNull(); + + if (originalValidity.equals(myValidity)) + ++successValidityQty; + else { + String message = String.format("myValidity is different for pgpUserId=%s: originalValidity=%d myValidity=%d", + pgpUserId, originalValidity, myValidity); + + if (originalValidity.equals(2) && myValidity.equals(0)) { + ++warningValidityQty; + logger.warn(message); + } + else if ((originalValidity & TRUST_FLAG_DISABLED) != 0 && (myValidity & TRUST_FLAG_DISABLED) != 0) { + ++warningValidityQty; + logger.warn(message); + } + else { + logger.error(message); + fatals.add(message); + } + } + } + + logger.info("updateMyProductiveTrustDb: successValidityQty={} warningValidityQty={} errorValidityQty={}", + successValidityQty, warningValidityQty, fatals.size()); + + if (! fatals.isEmpty()) + fail(fatals.toString()); + + assertThat(pgpUserId2ValidityMine).isEqualTo(pgpUserId2ValidityOriginal); + } + + private Map getPgpUserId2Validity() throws Exception { + Map pgpUserId2Validity = new HashMap<>(); + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + for (PgpKey pgpKey : pgpKeyRegistry.getMasterKeys()) { + for (PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { +// if (pgpUserId.getUserId() != null) { + int validity = trustDb.getValidity(pgpKey.getPublicKey(), pgpUserId.getNameHash()); + pgpUserId2Validity.put(pgpUserId, validity); +// } + } + } + } + return pgpUserId2Validity; + } + +} diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java new file mode 100644 index 0000000000..f167622db4 --- /dev/null +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java @@ -0,0 +1,410 @@ +package org.bouncycastle.openpgp.wot; + +import static org.assertj.core.api.Assertions.*; +import static org.bouncycastle.openpgp.PGPSignature.*; +import static org.bouncycastle.openpgp.wot.TrustConst.*; + +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.junit.Test; + +public class UpdateTrustDbTest extends AbstractTrustDbTest { + + @Test + public void directOnly() throws Exception { + PgpKey aliceKey = createPgpKey("alice"); + PgpKey bobKey = createPgpKey("bob"); + PgpKey cathrinKey = createPgpKey("cathrin"); + + bobKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, bobKey); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + } + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + } + } + + @Test + public void oneIndirection() throws Exception { + PgpKey aliceKey = createPgpKey("alice"); + PgpKey bobKey = createPgpKey("bob"); + PgpKey cathrinKey = createPgpKey("cathrin"); // not signed at all + PgpKey danielKey = createPgpKey("daniel"); + PgpKey emilKey = createPgpKey("emil"); + PgpKey frankKey = createPgpKey("frank"); + PgpKey georgKey = createPgpKey("georg"); + PgpKey hansKey = createPgpKey("hans"); + + bobKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, bobKey); // bob <= alice + danielKey = signPublicKey(bobKey, POSITIVE_CERTIFICATION, danielKey); // daniel <= bob <= alice + emilKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, emilKey); // emil <= cathrin ||| alice + frankKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, frankKey); // frank <= alice + + georgKey = signPublicKey(frankKey, POSITIVE_CERTIFICATION, georgKey); // georg <= frank <= alice + hansKey = signPublicKey(bobKey, CASUAL_CERTIFICATION, hansKey); // hans <= bob <= alice + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + + trustDb.setOwnerTrust(bobKey.getPublicKey(), TRUST_FULLY); + trustDb.setOwnerTrust(cathrinKey.getPublicKey(), TRUST_FULLY); + trustDb.setOwnerTrust(frankKey.getPublicKey(), TRUST_MARGINAL); + } + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // the signature type (CASUAL) has no effect :-( + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // behave like GnuPG! + } + } + + @Test + public void twoIndirections() throws Exception { + PgpKey aliceKey = createPgpKey("alice"); + + PgpKey bobKey = createPgpKey("bob"); + PgpKey cathrinKey = createPgpKey("cathrin"); + PgpKey danielKey = createPgpKey("daniel"); + PgpKey emilKey = createPgpKey("emil"); + + PgpKey frankKey = createPgpKey("frank"); + PgpKey georgKey = createPgpKey("georg"); + PgpKey hansKey = createPgpKey("hans"); + PgpKey idaKey = createPgpKey("ida"); + + PgpKey johnKey = createPgpKey("john"); + + bobKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, bobKey); // bob <= alice + cathrinKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, cathrinKey); // cathrin <= alice + danielKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, danielKey); // daniel <= alice + emilKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, emilKey); // emil <= alice + + frankKey = signPublicKey(bobKey, POSITIVE_CERTIFICATION, frankKey); // frank <= bob <= alice + georgKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, georgKey); // georg <= cathrin <= alice + hansKey = signPublicKey(danielKey, POSITIVE_CERTIFICATION, hansKey); // hans <= daniel <= alice + idaKey = signPublicKey(emilKey, POSITIVE_CERTIFICATION, idaKey); // hans <= emil <= alice + + johnKey = signPublicKey(frankKey, POSITIVE_CERTIFICATION, johnKey); // john <= frank <= bob <= alice + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + + trustDb.setOwnerTrust(bobKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(cathrinKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(danielKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(emilKey.getPublicKey(), TRUST_MARGINAL); + + trustDb.setOwnerTrust(frankKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(georgKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(hansKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(idaKey.getPublicKey(), TRUST_MARGINAL); + } + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + } + + frankKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, frankKey); // frank <= bob+cathrin <= alice + georgKey = signPublicKey(bobKey, POSITIVE_CERTIFICATION, georgKey); // georg <= bob+cathrin <= alice + hansKey = signPublicKey(emilKey, POSITIVE_CERTIFICATION, hansKey); // hans <= daniel+emil <= alice + idaKey = signPublicKey(danielKey, POSITIVE_CERTIFICATION, idaKey); // hans <= daniel+emil <= alice + + johnKey = signPublicKey(georgKey, POSITIVE_CERTIFICATION, johnKey); // john <= frank+georg <= bob+cathrin <= alice + johnKey = signPublicKey(hansKey, POSITIVE_CERTIFICATION, johnKey); // john <= frank+georg+hans <= bob+cathrin+daniel+emil <= alice + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + } + + + frankKey = signPublicKey(emilKey, POSITIVE_CERTIFICATION, frankKey); // frank <= bob+cathrin+emil <= alice + georgKey = signPublicKey(emilKey, POSITIVE_CERTIFICATION, georgKey); // georg <= bob+cathrin+emil <= alice + + // john <= frank+georg+hans <= bob+cathrin+daniel+emil <= alice + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + } + + hansKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, hansKey); // hans <= cathrin+daniel+emil <= alice + + // UNCHANGED: john <= frank+georg+hans <= bob+cathrin+daniel+emil <= alice + // only the validity of hans' key changed from MARGINAL to FULLY - and is now taken into account. + // => now there are 3 marginal signatures for john => it changes from MARGINAL to FULLY, too. + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + } + } + + @Test + public void threeIndirections() throws Exception { + PgpKey aliceKey = createPgpKey("alice"); + + PgpKey bobKey = createPgpKey("bob"); + PgpKey cathrinKey = createPgpKey("cathrin"); + PgpKey danielKey = createPgpKey("daniel"); + PgpKey[] level1Keys = new PgpKey[] { bobKey, cathrinKey, danielKey }; + + PgpKey emilKey = createPgpKey("emil"); + PgpKey frankKey = createPgpKey("frank"); + PgpKey georgKey = createPgpKey("georg"); + PgpKey[] level2Keys = new PgpKey[] { emilKey, frankKey, georgKey }; + + PgpKey hansKey = createPgpKey("hans"); + PgpKey idaKey = createPgpKey("ida"); + PgpKey johnKey = createPgpKey("john"); + PgpKey[] level3Keys = new PgpKey[] { hansKey, idaKey, johnKey }; + + PgpKey karlKey = createPgpKey("john"); + + bobKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, bobKey); // bob <= alice + cathrinKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, cathrinKey); // cathrin <= alice + danielKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, danielKey); // daniel <= alice + + + for (PgpKey signingKey : level1Keys) { + emilKey = signPublicKey(signingKey, POSITIVE_CERTIFICATION, emilKey); // emil <= bob+cathrin+daniel <= alice + frankKey = signPublicKey(signingKey, POSITIVE_CERTIFICATION, frankKey); // frank <= bob+cathrin+daniel <= alice + georgKey = signPublicKey(signingKey, POSITIVE_CERTIFICATION, georgKey); // georg <= bob+cathrin+daniel <= alice + } + + + for (PgpKey signedKey : level3Keys) { + signPublicKey(emilKey, POSITIVE_CERTIFICATION, signedKey); + signPublicKey(frankKey, POSITIVE_CERTIFICATION, signedKey); + } + + signPublicKey(georgKey, POSITIVE_CERTIFICATION, hansKey); + signPublicKey(georgKey, POSITIVE_CERTIFICATION, idaKey); + + for (PgpKey signingKey : level3Keys) + signPublicKey(signingKey, POSITIVE_CERTIFICATION, karlKey); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + + for (PgpKey key : level1Keys) + trustDb.setOwnerTrust(key.getPublicKey(), TRUST_MARGINAL); + + for (PgpKey key : level2Keys) + trustDb.setOwnerTrust(key.getPublicKey(), TRUST_MARGINAL); + + for (PgpKey key : level3Keys) + trustDb.setOwnerTrust(key.getPublicKey(), TRUST_MARGINAL); + } + + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + + assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + } + + + signPublicKey(georgKey, POSITIVE_CERTIFICATION, johnKey); + + + if (! SKIP_GPG_CHECK_TRUST_DB) { + runGpgCheckTrustDb(); + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); + } + } + + try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + trustDb.updateTrustDb(); + + assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + + assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + + assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); + } + } +} diff --git a/pgwot/src/test/resources/log4j.properties b/pgwot/src/test/resources/log4j.properties new file mode 100644 index 0000000000..974eedaa5c --- /dev/null +++ b/pgwot/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger=DEBUG, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n diff --git a/settings.gradle b/settings.gradle index 68bbc4bb9e..efd8975003 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,3 +3,4 @@ include "mail" include "pg" include "pkix" include "prov" +include "pgwot" From d3463f5c7169b49bc4f711dbc96878f68bbe9b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Fri, 18 Sep 2015 21:23:26 +0700 Subject: [PATCH 02/17] * Cleaned up the API. * Added lots of Javadoc. --- .../org/bouncycastle/openpgp/wot/Config.java | 5 + .../bouncycastle/openpgp/wot/OwnerTrust.java | 98 ++++++ .../bouncycastle/openpgp/wot/TrustConst.java | 5 +- .../org/bouncycastle/openpgp/wot/TrustDb.java | 330 +++++++++++++++--- .../openpgp/wot/TrustDbException.java | 5 + .../openpgp/wot/TrustDbIoException.java | 6 + .../bouncycastle/openpgp/wot/TrustModel.java | 11 +- .../bouncycastle/openpgp/wot/Validity.java | 95 +++++ .../wot/{ => internal}/PgpKeyTrust.java | 4 +- .../wot/{ => internal}/PgpUserIdTrust.java | 4 +- .../openpgp/wot/{ => internal}/TrustDbIo.java | 12 +- .../wot/{ => internal}/TrustRecord.java | 4 +- .../wot/{ => internal}/TrustRecordType.java | 4 +- .../openpgp/wot/{ => internal}/Util.java | 2 +- .../bouncycastle/openpgp/wot/key/PgpKey.java | 10 +- .../openpgp/wot/key/PgpKeyFingerprint.java | 6 +- .../openpgp/wot/key/PgpKeyId.java | 6 +- .../openpgp/wot/key/PgpKeyRegistry.java | 169 +++++++-- .../openpgp/wot/key/PgpUserId.java | 6 +- .../openpgp/wot/key/PgpUserIdNameHash.java | 22 +- .../openpgp/wot/key/package-info.java | 6 + .../openpgp/wot/package-info.java | 2 +- .../openpgp/wot/AbstractTrustDbTest.java | 2 +- .../wot/TrustDbProductiveFileTest.java | 7 +- .../openpgp/wot/UpdateTrustDbTest.java | 260 +++++++------- 25 files changed, 848 insertions(+), 233 deletions(-) create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java rename pgwot/src/main/java/org/bouncycastle/openpgp/wot/{ => internal}/PgpKeyTrust.java (92%) rename pgwot/src/main/java/org/bouncycastle/openpgp/wot/{ => internal}/PgpUserIdTrust.java (92%) rename pgwot/src/main/java/org/bouncycastle/openpgp/wot/{ => internal}/TrustDbIo.java (98%) rename pgwot/src/main/java/org/bouncycastle/openpgp/wot/{ => internal}/TrustRecord.java (99%) rename pgwot/src/main/java/org/bouncycastle/openpgp/wot/{ => internal}/TrustRecordType.java (95%) rename pgwot/src/main/java/org/bouncycastle/openpgp/wot/{ => internal}/Util.java (98%) create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java index de54d2ef35..cb27fa3799 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java @@ -1,5 +1,10 @@ package org.bouncycastle.openpgp.wot; +/** + * Configuration settings for the trust calculation. + * + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class Config implements TrustConst { public static final int TM_CLASSIC = 0; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java new file mode 100644 index 0000000000..1659c2dc16 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java @@ -0,0 +1,98 @@ +package org.bouncycastle.openpgp.wot; + +import java.util.HashMap; +import java.util.Map; + +/** + * The owner-trust is assigned to a key, but does not describe the key itself. + * It specifies how reliable the owner of this key is in his function as notary + * certifying other keys. + *

+ * When judging the owner-trust of a person, you should answer the following questions: + *

    + *
  • How well does this person understand OpenPGP? + *
  • How well does this person protect his computer's integrity and most importantly + * his private key? + *
  • How well does this person check the authenticity of another key before signing + * (= certifying) it? + *
+ *

+ * Whether you trust this person on a personal level, should have only a minor influence + * on the owner-trust. For example, you certainly trust your mother, but unless she's + * really computer-savvy, you likely assign a low owner-trust to her key. + * + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + * @see TrustDb#getOwnerTrust(org.bouncycastle.openpgp.wot.key.PgpKey) + */ +public enum OwnerTrust { + + /** + * It is unknown, how reliable the key's owner is as notary for other keys. + *

+ * This causes no transitive trust. + */ + UNKNOWN(TrustConst.TRUST_UNKNOWN), // 0 + + /** + * The key's owner should not be trusted. + *

+ * This causes no transitive trust. + */ + NEVER(TrustConst.TRUST_NEVER), // 3 + + /** + * The key's owner is a marginally trustable notary. Certifications of keys made by this key's owner can thus be trusted a little. + *

+ * This causes some transitive trust. Together with other "marginally" trusted owner's + * certifications, it might cause a key to be trusted fully. + */ + MARGINAL(TrustConst.TRUST_MARGINAL), // 4 + + /** + * The key's owner is a fully trusted notary. Certificaton of keys made by this key's owner are thus considered very reliable. + *

+ * This causes significant transitive trust. Depending on the settings, this is already + * enough for a certified key to be trusted fully, or it might require further signatures. + */ + FULLY(TrustConst.TRUST_FULLY), // 5 + + /** + * The key's owner can be ultimately trusted. One single signature of a notary whose key is marked 'ultimate' is sufficient + * for full transitive trust. + *

+ * Usually, the user himself marks all his own keys with this owner-trust. + */ + ULTIMATE(TrustConst.TRUST_ULTIMATE) // 6 + ; + + private final int numericValue; + + private static Map numericValue2OwnerTrust; + + private OwnerTrust(final int numericValue) { + this.numericValue = numericValue; + } + + public int getNumericValue() { + return numericValue; + } + + public static OwnerTrust fromNumericValue(final int numericValue) { + final OwnerTrust ownerTrust = getNumericValue2OwnerTrust().get(numericValue); + if (ownerTrust == null) + throw new IllegalArgumentException("numericValue unknown: " + numericValue); + + return ownerTrust; + } + + private static Map getNumericValue2OwnerTrust() { + if (numericValue2OwnerTrust == null) { + Map m = new HashMap<>(values().length); + for (OwnerTrust ownerTrust : values()) + m.put(ownerTrust.getNumericValue(), ownerTrust); + + numericValue2OwnerTrust = m; + } + return numericValue2OwnerTrust; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java index 830b228b37..bcfd196b2d 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java @@ -10,12 +10,11 @@ public interface TrustConst { int MAX_CACHE_SIZE = 1024 * 1024; - int TRUST_MASK = 15; /** o: not yet calculated/assigned */ int TRUST_UNKNOWN = 0; - /** e: calculation may be invalid */ - int TRUST_EXPIRED = 1; +// /** e: calculation may be invalid */ +// int TRUST_EXPIRED = 1; // unused?! gnupg seems to never assign this value... /** q: not enough information for calculation */ int TRUST_UNDEFINED = 2; /** n: never trust this pubkey */ diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java index 54623ee036..da130c1542 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.wot; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.File; import java.text.DateFormat; @@ -16,6 +16,11 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.wot.internal.PgpKeyTrust; +import org.bouncycastle.openpgp.wot.internal.PgpUserIdTrust; +import org.bouncycastle.openpgp.wot.internal.TrustDbIo; +import org.bouncycastle.openpgp.wot.internal.TrustRecord; +import org.bouncycastle.openpgp.wot.internal.TrustRecordType; import org.bouncycastle.openpgp.wot.key.PgpKey; import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; import org.bouncycastle.openpgp.wot.key.PgpKeyId; @@ -30,8 +35,8 @@ *

* An instance of this class is used for the following purposes: *

    - *
  • Read the validity of a {@linkplain #getValidity(PGPPublicKey) certain key}, - * {@linkplain #getValidity(PGPPublicKey, PgpUserIdNameHash) user-identity or user-attribute}. + *
  • Read the validity of a {@linkplain #getValidityRaw(PGPPublicKey) certain key}, + * {@linkplain #getValidityRaw(PGPPublicKey, PgpUserIdNameHash) user-identity or user-attribute}. *
  • Find out whether a key is {@linkplain #isDisabled(PGPPublicKey) disabled}. *
  • Mark a key {@linkplain #setDisabled(PGPPublicKey, boolean) disabled} or enabled. *
  • Set a key's {@linkplain #setOwnerTrust(PGPPublicKey, int) owner-trust} attribute. @@ -54,6 +59,13 @@ public class TrustDb implements AutoCloseable, TrustConst { private Set fullTrust; private DateFormat dateFormatIso8601WithTime; + /** + * Create a {@code TrustDb} instance with the given {@code trustdb.gpg} file and the given key-registry. + *

    + * Important: You must {@linkplain #close() close} this instance! + * @param file the trust-database-file ({@code trustdb.gpg}). Must not be null. + * @param pgpKeyRegistry the key-registry. Must not be null. + */ public TrustDb(final File file, final PgpKeyRegistry pgpKeyRegistry) { assertNotNull("file", file); this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); @@ -115,53 +127,87 @@ else if (record.getType() == TrustRecordType.VALID) { } /** - * Gets the assigned ownertrust value for the given public key. - * The key should be the master key. + * Gets the assigned owner-trust value for the given public key. + *

    + * This value specifies how much the user trusts the owner of the given key in his function as notary + * certifying other keys. + * @param pgpKey the key whose owner-trust should be looked up. Must not be null. + * @return the owner-trust. Never null. If none has been assigned, before, this + * method returns {@link OwnerTrust#UNKNOWN UNKNOWN}. + * @see #setOwnerTrust(PgpKey, OwnerTrust) + * @see #getOwnerTrust(PGPPublicKey) + */ + public OwnerTrust getOwnerTrust(PgpKey pgpKey) { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return getOwnerTrust(pgpKey.getPublicKey()); + } + + /** + * Sets the given key's owner-trust. + *

    + * This value specifies how much the user trusts the owner of the given key in his function as notary + * certifying other keys. + *

    + * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. + * @param pgpKey the key whose owner-trust is to be set. Must not be null. + * @param ownerTrust the owner-trust to be assigned. Must not be null. + * @see #getOwnerTrust(PgpKey) + * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) + */ + public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) { + assertNotNull("pgpKey", pgpKey); + assertNotNull("ownerTrust", ownerTrust); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); + } + + /** + * Gets the assigned owner-trust value for the given public key. + *

    + * This value specifies how much the user trusts the owner of the given key in his function as notary + * certifying other keys. + *

    + * The given key should be a master key. + * @param publicKey the key whose owner-trust should be looked up. Must not be null. + * @return the owner-trust. Never null. If none has been assigned, before, this + * method returns {@link OwnerTrust#UNKNOWN UNKNOWN}. + * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) + * @see #getOwnerTrust(PgpKey) */ - public int getOwnerTrust(final PGPPublicKey publicKey) { + public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) { assertNotNull("publicKey", publicKey); // if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) // return TRUST_UNKNOWN; // TODO maybe we should support other trust models... TrustRecord.Trust trust = getTrustByPublicKey(publicKey); if (trust == null) - return TRUST_UNKNOWN; + return OwnerTrust.UNKNOWN; - return trust.getOwnerTrust() & TRUST_MASK; + return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); } /** * Sets the given key's owner-trust. *

    * This value specifies how much the user trusts the owner of the given key in his function as notary - * certifying other keys. One of the following constants can be passed: - *

      - *
    • {@link TrustConst#TRUST_UNKNOWN TRUST_UNKNOWN} - *
    • {@link TrustConst#TRUST_NEVER TRUST_NEVER} - *
    • {@link TrustConst#TRUST_MARGINAL TRUST_MARGINAL} - *
    • {@link TrustConst#TRUST_FULLY TRUST_FULLY} - *
    • {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE} - *
    + * certifying other keys. *

    * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. + *

    + * The given key should be a master key. * @param publicKey the key whose owner-trust is to be set. Must not be null. - * @param ownerTrust the owner-trust to be assigned. + * @param ownerTrust the owner-trust to be assigned. Must not be null. + * @see #getOwnerTrust(PGPPublicKey) + * @see #setOwnerTrust(PgpKey, OwnerTrust) */ - public void setOwnerTrust(final PGPPublicKey publicKey, final int ownerTrust) { + public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) { assertNotNull("publicKey", publicKey); - assertNonNegativeShort("ownerTrust", ownerTrust); - switch (ownerTrust) { - case TRUST_UNKNOWN: // 0 - case TRUST_NEVER: // 3 - case TRUST_MARGINAL: // 4 - case TRUST_FULLY: // 5 - case TRUST_ULTIMATE: // 6 - break; - default: - throw new IllegalArgumentException(String.format( - "ownerTrust=%d invalid! Must be one of: TRUST_UNKNOWN, TRUST_NEVER, TRUST_MARGINAL, TRUST_FULLY, TRUST_ULTIMATE", - ownerTrust)); - } + assertNotNull("ownerTrust", ownerTrust); TrustRecord.Trust trust = getTrustByPublicKey(publicKey); if (trust == null) { @@ -172,7 +218,7 @@ public void setOwnerTrust(final PGPPublicKey publicKey, final int ownerTrust) { int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; - trust.setOwnerTrust((short) (ownerTrust | ownerTrustAdditionalFlags)); + trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); trustDbIo.putTrustRecord(trust); markTrustDbStale(); @@ -185,13 +231,131 @@ protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) { return trust; } - public synchronized int getValidity(final PGPPublicKey publicKey) { - return getValidity(publicKey, (PgpUserIdNameHash) null); + /** + * Gets the validity of the given key. + *

    + * The validity of a key is the highest validity of all its user-identities (and -attributes). It can be one of + * {@link Validity}'s numeric values (see also the {@link TrustConst} constants) and + * it additionally contains the following bit flags: + *

      + *
    • {@link TrustConst#TRUST_FLAG_DISABLED} - corresponds to {@link #isDisabled(PGPPublicKey)}. + *
    • {@link TrustConst#TRUST_FLAG_REVOKED} - corresponds to {@link PGPPublicKey#hasRevocation()}. + *
    • {@link TrustConst#TRUST_FLAG_PENDING_CHECK} - corresponds to {@link #isTrustDbStale()}. + *
    + *

    + * This method does not calculate the validity! It does solely look it up in the trust-database. + * The validity is (re)calculated by {@link #updateTrustDb()}. + * @param publicKey the key whose validity is to be returned. Must not be null. + * @return the validity with bit flags. + * @see #getValidityRaw(PGPPublicKey, PgpUserIdNameHash) + * @deprecated This method exists for compatibility with GnuPG and for easier comparisons between + * GnuPG's calculations and the calculations of this code. Do not use it in your code! + * Use {@link #getValidity(PGPPublicKey)} instead. + */ + @Deprecated + public synchronized int getValidityRaw(final PGPPublicKey publicKey) { + assertNotNull("publicKey", publicKey); + return _getValidity(publicKey, (PgpUserIdNameHash) null, true); + } + + /** + * Gets the validity of the given user-identity. + *

      + *
    • {@link TrustConst#TRUST_FLAG_DISABLED} - corresponds to {@link #isDisabled(PGPPublicKey)}. + *
    • {@link TrustConst#TRUST_FLAG_REVOKED} - corresponds to {@link PGPPublicKey#hasRevocation()}. + *
    • {@link TrustConst#TRUST_FLAG_PENDING_CHECK} - corresponds to {@link #isTrustDbStale()}. + *
    + *

    + * This method does not calculate the validity! It does solely look it up in the trust-database. + * The validity is (re)calculated by {@link #updateTrustDb()}. + * @param publicKey the key whose validity is to be returned. Must not be null. + * @param pgpUserIdNameHash user-id's (or user-attribute's) name-hash. Must not be null. + * @return the validity with bit flags. + * @see #getValidityRaw(PGPPublicKey) + * @deprecated This method exists for compatibility with GnuPG and for easier comparisons between + * GnuPG's calculations and the calculations of this code. Do not use it in your code! + * Use {@link #getValidity(PGPPublicKey, PgpUserIdNameHash)} instead. + */ + @Deprecated + public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + return _getValidity(publicKey, pgpUserIdNameHash, true); + } + + /** + * Gets the validity of the given key. + *

    + * The validity of a key is the highest validity of all its user-identities (and -attributes). + *

    + * This method does not calculate the validity! It does solely look it up in the trust-database. + * The validity is (re)calculated by {@link #updateTrustDb()}. + * @param pgpKey the key whose validity to look up. Must not be null. + * @return the validity of the given {@code publicKey}. Never null. + * @see #getValidity(PgpKey, PgpUserIdNameHash) + * @see #getValidity(PGPPublicKey) + */ + public Validity getValidity(final PgpKey pgpKey) { + assertNotNull("pgpKey", pgpKey); + return getValidity(pgpKey.getPublicKey()); + } + + /** + * Gets the validity of the given user-identity (or -attribute). + *

    + * This method does not calculate the validity! It does solely look it up in the trust-database. + * The validity is (re)calculated by {@link #updateTrustDb()}. + * @param pgpUserId the user-identity (or -attribute) whose validity to + * look up. Must not be null. + * @return the validity of the given user-identity. Never null. + * @see #getValidity(PgpKey) + * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) + */ + public Validity getValidity(final PgpUserId pgpUserId) { + assertNotNull("pgpUserId", pgpUserId); + return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); } - public synchronized int getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) { + /** + * Gets the validity of the given key. + *

    + * The validity of a key is the highest validity of all its user-identities (and -attributes). + *

    + * This method does not calculate the validity! It does solely look it up in the trust-database. + * The validity is (re)calculated by {@link #updateTrustDb()}. + * @param publicKey the key whose validity to look up. Must not be null. + * @return the validity of the given {@code publicKey}. Never null. + * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) + */ + public Validity getValidity(final PGPPublicKey publicKey) { + assertNotNull("publicKey", publicKey); + final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); + return Validity.fromNumericValue(numericValue); + } + + /** + * Gets the validity of the given user-identity (or -attribute). + *

    + * This method does not calculate the validity! It does solely look it up in the trust-database. + * The validity is (re)calculated by {@link #updateTrustDb()}. + * @param publicKey the key whose validity to look up. Must not be null. + * @param pgpUserIdNameHash the name-hash of the user-identity (or -attribute) whose validity to + * look up. Must not be null. + * @return the validity of the given user-identity. Never null. + * @see #getValidity(PGPPublicKey) + */ + public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) { assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); + return Validity.fromNumericValue(numericValue); + } + /** + * Ported from {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} + */ + protected synchronized int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, final boolean withFlags) { + assertNotNull("publicKey", publicKey); TrustRecord.Trust trust = getTrustByPublicKey(publicKey); if (trust == null) return TRUST_UNKNOWN; @@ -199,6 +363,10 @@ public synchronized int getValidity(final PGPPublicKey publicKey, final PgpUserI // Loop over all user IDs long recordNum = trust.getValidList(); int validity = 0; + int flags = 0; + // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust + // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, + // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). while (recordNum != 0) { TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); assertNotNull("valid", valid); @@ -208,26 +376,31 @@ public synchronized int getValidity(final PGPPublicKey publicKey, final PgpUserI // user ID ONLY. If the namehash is not found, then there // is no validity at all (i.e. the user ID wasn't signed). if (pgpUserIdNameHash.equals(valid.getNameHash())) { - validity = valid.getValidity(); + validity = valid.getValidity() & TRUST_MASK; + flags = valid.getValidity() & ~TRUST_MASK; break; } } else { // If no user ID is given, we take the maximum validity over all user IDs validity = Math.max(validity, valid.getValidity() & TRUST_MASK); + flags |= valid.getValidity() & ~TRUST_MASK; } recordNum = valid.getNext(); } - if ( (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0 ) - validity |= TRUST_FLAG_DISABLED; + if (withFlags) { + validity |= flags; - if (publicKey.hasRevocation()) - validity |= TRUST_FLAG_REVOKED; + if ( (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0 ) + validity |= TRUST_FLAG_DISABLED; - if (isTrustDbStale()) - validity |= TRUST_FLAG_PENDING_CHECK; + if (publicKey.hasRevocation()) + validity |= TRUST_FLAG_REVOKED; + if (isTrustDbStale()) + validity |= TRUST_FLAG_PENDING_CHECK; + } return validity; } @@ -289,6 +462,8 @@ private static void assertNonNegativeShort(final String name, final int value) { /** * Marks all those keys that we have a secret key for as ultimately trusted. If we have a secret/private key, * we assume it to be *our* key and we always trust ourselves. + * @param onlyIfMissing whether only those keys' owner-trust should be set which do not yet have + * an owner-trust assigned. */ public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) { for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) { @@ -343,7 +518,41 @@ public boolean isExpired(PGPPublicKey publicKey) { // It's a very small number of keys only, hence I ignore it for now ;-) } - public boolean isDisabled(PGPPublicKey publicKey) { + /** + * Determines whether the specified key is marked as disabled. + * @param pgpKey the key whose status to query. Must not be null. + * @return true, if the key is marked as disabled; false, if the key is enabled. + */ + public boolean isDisabled(PgpKey pgpKey) { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return isDisabled(pgpKey.getPublicKey()); + } + + /** + * Enables or disabled the specified key. + * @param pgpKey the key whose status to query. Must not be null. + * @param disabled true to disable the key; false to enable it. + */ + public void setDisabled(PgpKey pgpKey, final boolean disabled) { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setDisabled(pgpKey.getPublicKey(), disabled); + } + + /** + * Determines whether the specified key is marked as disabled. + *

    + * The key should be a master-key. + * @param publicKey the key whose status to query. Must not be null. + * This should be a master-key. + * @return true, if the key is marked as disabled; false, if the key is enabled. + */ + public boolean isDisabled(final PGPPublicKey publicKey) { assertNotNull("publicKey", publicKey); TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); if (trust == null) @@ -352,7 +561,15 @@ public boolean isDisabled(PGPPublicKey publicKey) { return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; } - public void setDisabled(PGPPublicKey publicKey, boolean disabled) { + /** + * Enables or disabled the specified key. + *

    + * The key should be a master-key. + * @param publicKey the key whose status to query. Must not be null. + * This should be a master-key. + * @param disabled true to disable the key; false to enable it. + */ + public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) { assertNotNull("publicKey", publicKey); TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); if (trust == null) { @@ -372,6 +589,17 @@ public void setDisabled(PGPPublicKey publicKey, boolean disabled) { trustDbIo.flush(); } + /** + * Determines if the trust-database is stale. It becomes stale, if it is either + * explicitly {@linkplain #markTrustDbStale() marked stale} or if a key expires. + *

    + * Important: It does not become stale when a key ring file is modified! Thus, when adding new + * keys, {@link #markTrustDbStale()} or {@link #updateTrustDb()} must be invoked. + * @return true, if the trust-database is stale; false, if it is up-to-date. + * @see #markTrustDbStale() + * @see #updateTrustDb() + * @see #updateTrustDbIfNeeded() + */ public synchronized boolean isTrustDbStale() { final Config config = Config.getInstance(); final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); @@ -629,14 +857,14 @@ private void validateKey(final PgpKey pgpKey) { if (signingKey == null) continue; - final int signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); + final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) - && signingOwnerTrust != TRUST_ULTIMATE) { - // It's *not* our own key [*not* TRUST_ULTIMATE] - hence we ignore the self-signature. + && signingOwnerTrust != OwnerTrust.ULTIMATE) { + // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. continue; } - int signingValidity = getValidity(signingKey.getPublicKey()) & TRUST_MASK; + int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; if (signingValidity <= TRUST_MARGINAL) { // If the signingKey is trusted only marginally or less, we ignore the certification completely. // Only fully trusted keys are taken into account for transitive trust. @@ -645,13 +873,13 @@ private void validateKey(final PgpKey pgpKey) { // The owner-trust of the signing key is relevant. switch (signingOwnerTrust) { - case TRUST_ULTIMATE: + case ULTIMATE: pgpUserIdTrust.incUltimateCount(); break; - case TRUST_FULLY: + case FULLY: pgpUserIdTrust.incFullCount(); break; - case TRUST_MARGINAL: + case MARGINAL: pgpUserIdTrust.incMarginalCount(); break; default: // ignoring! diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java index ac56d4f699..bf2b95e93b 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java @@ -1,5 +1,10 @@ package org.bouncycastle.openpgp.wot; +/** + * Exception thrown by {@link TrustDb} and related classes. + * + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class TrustDbException extends RuntimeException { private static final long serialVersionUID = 1L; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java index 3f6036e5c9..7b822750fa 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java @@ -1,5 +1,11 @@ package org.bouncycastle.openpgp.wot; +import org.bouncycastle.openpgp.wot.internal.TrustDbIo; + +/** + * Exception thrown by {@link TrustDbIo} when reading from or writing to the trust database failed. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class TrustDbIoException extends TrustDbException { private static final long serialVersionUID = 1L; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java index 76204279df..6076178539 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java @@ -1,7 +1,14 @@ package org.bouncycastle.openpgp.wot; -import static org.bouncycastle.openpgp.wot.Util.*; - +import static org.bouncycastle.openpgp.wot.internal.Util.*; + +/** + * Trust-model specifying the policy and algorithm of trust/validity calculations. + *

    + * OpenPGP/GnuPG supports multiple trust models. This implementation, however, currently supports + * {@link #PGP} only. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public enum TrustModel { CLASSIC(0, "classic"), diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java new file mode 100644 index 0000000000..7a6e199879 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java @@ -0,0 +1,95 @@ +package org.bouncycastle.openpgp.wot; + +import java.util.HashMap; +import java.util.Map; + +/** + * Validity of a key or user-identity/-attribute. + *

    + * The validity is calculated by {@link TrustDb#updateTrustDb()} and can be queried by + * its {@link TrustDb#getValidity(org.bouncycastle.openpgp.wot.key.PgpKey) getValidity(PgpKey)} + * or another overloaded {@code getValidity(...)} method. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ +public enum Validity { + + /** + * The key/user-identity/user-attribute is not valid or the validity is not known. + *

    + * A user should be warned when using or encountering such a key + * (e.g. a red indication colour is a good idea). + */ + NONE(TrustConst.TRUST_UNKNOWN), // 0 + +// EXPIRED(TrustConst.TRUST_EXPIRED), // 1 + + /** + * The key/user-identity/user-attribute is not valid. The 'undefined' state results from + * trust originating transitively from keys being expired, revoked or otherwise not trustworthy, + * anymore. + *

    + * A user should be warned when using or encountering such a key + * (e.g. a red indication colour is a good idea). + */ + UNDEFINED(TrustConst.TRUST_UNDEFINED), // 2 + + /** + * The key/user-identity/user-attribute is probably valid. There is some indication for + * its authenticity. + *

    + * A user might get a mild warning when using or encountering such a key + * (e.g. a yellow/orange indication colour), though it is + * probably fine. + */ + MARGINAL(TrustConst.TRUST_MARGINAL), // 4 + + /** + * The key/user-identity/user-attribute is valid. There is strong indication for + * its authenticity. + *

    + * A user should see some positive confirmation (e.g. a green indication colour) when + * using or encountering such a key. + */ + FULLY(TrustConst.TRUST_FULLY), // 5 + + /** + * The key/user-identity/user-attribute is definitely valid - probably it's belonging to + * the user himself. There is no doubt about its authenticity. + *

    + * A user should see some positive confirmation (e.g. a green indication colour) when + * using or encountering such a key. + */ + ULTIMATE(TrustConst.TRUST_ULTIMATE) // 6 + ; + + private final int numericValue; + + private static Map numericValue2Validity; + + private Validity(final int numericValue) { + this.numericValue = numericValue; + } + + public int getNumericValue() { + return numericValue; + } + + public static Validity fromNumericValue(final int numericValue) { + final Validity validity = getNumericValue2Validity().get(numericValue); + if (validity == null) + throw new IllegalArgumentException("numericValue unknown: " + numericValue); + + return validity; + } + + private static Map getNumericValue2Validity() { + if (numericValue2Validity == null) { + Map m = new HashMap<>(values().length); + for (Validity ownerTrust : values()) + m.put(ownerTrust.getNumericValue(), ownerTrust); + + numericValue2Validity = m; + } + return numericValue2Validity; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java similarity index 92% rename from pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java rename to pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java index 2fa3038862..5bfd1c81e8 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpKeyTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java @@ -1,6 +1,6 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.util.Collection; import java.util.Collections; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java similarity index 92% rename from pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java rename to pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java index 011ea241f5..457542ddcd 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/PgpUserIdTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java @@ -1,6 +1,6 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import org.bouncycastle.openpgp.wot.key.PgpUserId; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java similarity index 98% rename from pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java rename to pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java index 66e6ec044c..e520fd385b 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIo.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java @@ -1,6 +1,6 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.EOFException; import java.io.File; @@ -16,7 +16,11 @@ import java.util.TreeMap; import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.wot.TrustRecord.HashLst; +import org.bouncycastle.openpgp.wot.Config; +import org.bouncycastle.openpgp.wot.TrustConst; +import org.bouncycastle.openpgp.wot.TrustDb; +import org.bouncycastle.openpgp.wot.TrustDbIoException; +import org.bouncycastle.openpgp.wot.internal.TrustRecord.HashLst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +51,8 @@ public class TrustDbIo implements AutoCloseable, TrustConst { /** * Create an instance of {@code TrustDbIo} with the given file (usually named {@code trustdb.gpg}). + *

    + * Important: You must {@linkplain #close() close} this instance! * @param file the file to read from and write to. Must not be null. Is created, if * not yet existing. * @throws TrustDbIoException if reading from/writing to the {@code trustdb.gpg} failed. diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java similarity index 99% rename from pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java rename to pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java index 75ee467695..666de9bef9 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecord.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java @@ -1,8 +1,10 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; import java.util.Arrays; import java.util.Date; +import org.bouncycastle.openpgp.wot.TrustConst; + public abstract class TrustRecord implements TrustConst { protected long recordNum = -1; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java similarity index 95% rename from pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java rename to pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java index 3f7da8adae..489a949c47 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustRecordType.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java @@ -1,6 +1,6 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.util.Collections; import java.util.HashMap; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java similarity index 98% rename from pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java rename to pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java index 74e465e91d..2492e53131 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Util.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java @@ -1,4 +1,4 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; public class Util { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java index 30f54e17e9..a5a7d8ce6f 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.wot.key; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.util.ArrayList; import java.util.Collections; @@ -15,6 +15,10 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +/** + * OpenPGP key or key pair (if both public and secret key are present). + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class PgpKey { private final PgpKeyId pgpKeyId; @@ -100,8 +104,8 @@ public List getPgpUserIds() { /** * Gets the master-key for this key. - * @return the master-key for this key. Always null, if this is a master-key. Never null, if - * this is a sub-key. + * @return the master-key for this key. Always null, if this is a master-key. + * Never null, if this is a sub-key. * @see #getSubKeyIds() * @see #getSubKeys() */ diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java index 418eba0a17..0fce858265 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java @@ -1,12 +1,16 @@ package org.bouncycastle.openpgp.wot.key; import static java.util.Arrays.*; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.Arrays; +/** + * An OpenPGP key's fingerprint. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class PgpKeyFingerprint implements Comparable, Serializable { private static final long serialVersionUID = 1L; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java index 66c19786be..0e384355cb 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java @@ -1,10 +1,14 @@ package org.bouncycastle.openpgp.wot.key; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.Serializable; import java.lang.ref.WeakReference; +/** + * An OpenPGP key's (unique) identifier. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class PgpKeyId implements Comparable, Serializable { private static final long serialVersionUID = 1L; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index 74f34b1528..fe2e4cc6f3 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.wot.key; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.BufferedInputStream; import java.io.File; @@ -32,14 +32,30 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Registry providing fast access to the keys of a public and a secret key ring collection. + *

    + * An {@code PgpKeyRegistry} reads the {@code pubring.gpg} and {@code secring.gpg} + * (normally located in {@code ~/.gnupg/}) and organizes them in {@link PgpKey} instances. + * It then provides fast lookup by key-id or fingerprint via {@link #getPgpKey(PgpKeyId)} + * or {@link #getPgpKey(PgpKeyFingerprint)}. + *

    + * The {@code PgpKeyRegistry} tracks the timestamps of the key ring collection files. If + * one of the files changes, i.e. the timestamp changes, the files are re-loaded. + * But beware: The file system's timestamps usually have a pretty bad resolution (of + * 1 or even 2 seconds). Therefore, it may happen that a modification goes undetected, + * if multiple changes occur within the resolution. + * + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class PgpKeyRegistry { private static final Logger logger = LoggerFactory.getLogger(PgpKeyRegistry.class); private final File pubringFile; private final File secringFile; - private long pubringFileLastModified; - private long secringFileLastModified; + private long pubringFileLastModified = Long.MIN_VALUE; + private long secringFileLastModified = Long.MIN_VALUE; private Map pgpKeyFingerprint2pgpKey; // all keys private Map pgpKeyId2pgpKey; // all keys @@ -47,20 +63,47 @@ public class PgpKeyRegistry { private Map> signingKeyId2signedKeyIds; + /** + * Creates an instance of {@code PgpKeyRegistry} with the given public and secret key ring + * collection files. + * @param pubringFile the file containing the public keys - usually named {@code pubring.gpg} + * (located in {@code ~/.gnupg/}). Must not be null. The file does not need to exist, though. + * @param secringFile the file containing the secret keys - usually named {@code secring.gpg} + * (located in {@code ~/.gnupg/}). Must not be null. The file does not need to exist, though. + */ public PgpKeyRegistry(File pubringFile, File secringFile) { this.pubringFile = assertNotNull("pubringFile", pubringFile); this.secringFile = assertNotNull("secringFile", secringFile); } + /** + * Gets the file containing the public keys - usually named {@code pubring.gpg} + * (located in {@code ~/.gnupg/}). + * @return the file containing the public keys. Never null. + */ public File getPubringFile() { return pubringFile; } + /** + * Gets the file containing the secret keys - usually named {@code secring.gpg} + * (located in {@code ~/.gnupg/}). + * @return the file containing the secret keys. Never null. + */ public File getSecringFile() { return secringFile; } - public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) { + /** + * Gets the key with the given ID. If no such key exists, an {@link IllegalArgumentException} is thrown. + *

    + * It makes no difference to this method whether the key is a master-key or a sub-key. + * @param pgpKeyId the key's ID. Must not be null. + * @return the key identified by the given {@code pgpKeyId}. Never null. + * @throws IllegalArgumentException if the given {@code pgpKeyId} is null or + * there is no key known with this ID. + */ + public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentException { final PgpKey pgpKey = getPgpKey(pgpKeyId); if (pgpKey == null) throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); @@ -68,14 +111,31 @@ public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) { return pgpKey; } - public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) { + /** + * Gets the key with the given ID. If no such key exists, null is returned. + *

    + * It makes no difference to this method whether the key is a master-key or a sub-key. + * @param pgpKeyId the key's ID. Must not be null. + * @return the key identified by the given {@code pgpKeyId}. May be null. + * @throws IllegalArgumentException if the given {@code pgpKeyId} is null. + */ + public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException { assertNotNull("pgpKeyId", pgpKeyId); loadIfNeeded(); final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); return pgpKey; } - public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) { + /** + * Gets the key with the given fingerprint. If no such key exists, an {@link IllegalArgumentException} is thrown. + *

    + * It makes no difference to this method whether the key is a master-key or a sub-key. + * @param pgpKeyFingerprint the key's fingerprint. Must not be null. + * @return the key identified by the given {@code pgpKeyFingerprint}. Never null. + * @throws IllegalArgumentException if the given {@code pgpKeyFingerprint} is null or + * there is no key known with this fingerprint. + */ + public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException { final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); if (pgpKey == null) throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); @@ -83,23 +143,47 @@ public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) { return pgpKey; } - public void markStale() { - pubringFileLastModified = 0; - secringFileLastModified = 0; - } - - public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) { + /** + * Gets the key with the given fingerprint. If no such key exists, null is returned. + *

    + * It makes no difference to this method whether the key is a master-key or a sub-key. + * @param pgpKeyFingerprint the key's fingerprint. Must not be null. + * @return the key identified by the given {@code pgpKeyFingerprint}. May be null. + * @throws IllegalArgumentException if the given {@code pgpKeyFingerprint} is null. + */ + public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException { assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); loadIfNeeded(); final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); return pgpKey; } + /** + * Gets all master-keys. Their sub-keys are accessible via {@link PgpKey#getSubKeys()}. + * @return all master-keys. Never null. + */ public synchronized Collection getMasterKeys() { loadIfNeeded(); return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); } + /** + * Marks this registry stale - causing it to reload at the next read access. + *

    + * If a modification of a key ring file happens, this modification is usually detected automatically, + * rendering this registry stale implicitly. However, a change is not reliably detected, because + * the file system's timestamp resolution is usually 1 second or even worse. Multiple changes within + * this resolution might thus go undetected. In order to make sure that a key ring file modification + * reliably causes this registry to reload, this method can be invoked. + */ + public void markStale() { + pubringFileLastModified = Long.MIN_VALUE; + secringFileLastModified = Long.MIN_VALUE; + } + + /** + * Loads the key ring files, if they were not yet read or if this registry is stale. + */ protected synchronized void loadIfNeeded() { if (pgpKeyId2pgpKey == null || getPubringFile().lastModified() != pubringFileLastModified @@ -111,6 +195,9 @@ protected synchronized void loadIfNeeded() { logger.trace("loadIfNeeded: *not* invoking load()."); } + /** + * Loads the key ring files. + */ protected synchronized void load() { pgpKeyFingerprint2pgpKey = null; final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); @@ -244,7 +331,18 @@ else if (keyRing instanceof PGPPublicKeyRing) return masterKey; } + /** + * Gets all those keys' fingerprints whose keys were signed (certified) by the key identified by the given fingerprint. + *

    + * Usually, the fingerprint specified should identify a master-key and usually only master-key-fingerprints are + * returned by this method. + * @param signingPgpKeyFingerprint the fingerprint of the key having signed all those keys that we're interested in. + * Must not be null. + * @return the fingerprints of all those keys which have been signed (certified) by the key identified by + * {@code signingPgpKeyFingerprint}. Never null, but maybe empty. + */ public synchronized Set getPgpKeyFingerprintsSignedBy(final PgpKeyFingerprint signingPgpKeyFingerprint) { + assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); if (signingPgpKey == null) return Collections.emptySet(); @@ -261,6 +359,16 @@ public synchronized Set getPgpKeyFingerprintsSignedBy(final P return Collections.unmodifiableSet(result); } + /** + * Gets all those keys' IDs whose keys were signed (certified) by the key identified by the given ID. + *

    + * Usually, the ID specified should identify a master-key and usually only master-key-IDs are + * returned by this method. + * @param signingPgpKeyId the ID of the key having signed all those keys that we're interested in. + * Must not be null. + * @return the IDs of all those keys which have been signed (certified) by the key identified by + * {@code signingPgpKeyId}. Never null, but maybe empty. + */ public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) { final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); if (pgpKeyIds == null) @@ -309,6 +417,12 @@ else if (pgpUserId.getUserAttribute() != null) { return signingKeyId2signedKeyIds; } + /** + * Gets the signatures certifying the authenticity of the given user-ID. + * @param pgpUserId the user-ID whose certifications should be returned. Must not be null. + * @return the certifications authenticating the given {@code pgpUserId}. Never null. + * Because every user-ID is normally at least signed by the owning key, it is normally never empty, too. + */ @SuppressWarnings("unchecked") public synchronized List getSignatures(final PgpUserId pgpUserId) { assertNotNull("pgpUserId", pgpUserId); @@ -338,17 +452,6 @@ else if (pgpUserId.getUserAttribute() != null) { else throw new IllegalStateException("WTF?!"); -// // It seems, there are both: certifications for individual user-ids and certifications for the -// // entire key. I therefore first take the individual ones (above) into account then and then -// // the ones for the entire key (below). -// for (Iterator it = nullToEmpty(publicKey.getSignatures()); it.hasNext(); ) { -// final PGPSignature pgpSignature = (PGPSignature) it.next(); -// if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) { -// pgpSignatures.put(pgpSignature, pgpSignature); -// result.add(pgpSignature); -// } -// } - return result; } @@ -370,11 +473,29 @@ private void enlistInSigningKey2signedKeyIds(final Map> signedKeyIds.add(pgpKey.getPgpKeyId()); } - public boolean isCertification(PGPSignature pgpSignature) { + /** + * Determines whether the given signature is a certification. + *

    + * A certification is a signature indicating that a certain key or user-identity is authentic. + * @param pgpSignature the signature to be checked. Must not be null. + * @return true, if the signature is a certification; false, if it is + * of a different type. + * @see #isCertification(int) + */ + public boolean isCertification(final PGPSignature pgpSignature) { assertNotNull("pgpSignature", pgpSignature); return isCertification(pgpSignature.getSignatureType()); } + /** + * Determines whether the given signature-type indicates a certification. + *

    + * A certification is a signature indicating that a certain key or user-identity is authentic. + * @param pgpSignatureType the type of the signature - like {@link PGPSignature#DEFAULT_CERTIFICATION} + * or other constants (used by the property {@link PGPSignature#getSignatureType()}, for example). + * @return true, if the given signature-type means certification; false otherwise. + * @see #isCertification(PGPSignature) + */ public boolean isCertification(int pgpSignatureType) { return PGPSignature.DEFAULT_CERTIFICATION == pgpSignatureType || PGPSignature.NO_CERTIFICATION == pgpSignatureType diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java index 080432f72e..9e5363b14c 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java @@ -1,9 +1,13 @@ package org.bouncycastle.openpgp.wot.key; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +/** + * User-identity or user-attribute of an OpenPGP key. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class PgpUserId { private final PgpKey pgpKey; private final String userId; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java index eec3f68d49..6352df5e0b 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java @@ -1,7 +1,7 @@ package org.bouncycastle.openpgp.wot.key; import static java.util.Arrays.*; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.Serializable; import java.lang.ref.WeakReference; @@ -13,6 +13,13 @@ import org.bouncycastle.crypto.digests.RIPEMD160Digest; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +/** + * Hash used as identifier of a user-identity or user-attribute. + *

    + * Use {@link #createFromUserId(String)} or {@link #createFromUserAttribute(PGPUserAttributeSubpacketVector)} + * to create an instance. + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ public class PgpUserIdNameHash implements Comparable, Serializable { private static final long serialVersionUID = 1L; @@ -111,6 +118,11 @@ private String _toHumanString() { return sb.toString(); } + /** + * Creates an instance of {@code PgpUserIdNameHash} for the given user-identity. + * @param userId the user-identity for which to create a name-hash instance. Must not be null. + * @return the name-hash. Never null. + */ public static PgpUserIdNameHash createFromUserId(final String userId) { assertNotNull("userId", userId); @@ -123,12 +135,18 @@ public static PgpUserIdNameHash createFromUserId(final String userId) { return new PgpUserIdNameHash(out); } + /** + * Creates an instance of {@code PgpUserIdNameHash} for the given user-attribute. A user-attribute usually + * is an image. + * @param userAttribute the user-attribute for which to create a name-hash instance. Must not be null. + * @return the name-hash. Never null. + */ public static PgpUserIdNameHash createFromUserAttribute(final PGPUserAttributeSubpacketVector userAttribute) { assertNotNull("userAttribute", userAttribute); final RIPEMD160Digest digest = new RIPEMD160Digest(); - // TODO this needs to be extended, if there is ever any other attribute possible, too! + // TODO this needs to be extended, if there is ever any other attribute type (other than image) possible, too! // Currently, image seems to be the only supported attribute. Alternatively, we could get the data via reflection... final UserAttributeSubpacket subpacket = userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE); assertNotNull("subpacket", subpacket); diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java new file mode 100644 index 0000000000..f0298bf82f --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java @@ -0,0 +1,6 @@ +/** + * Key management used by the OpenPGP Web Of Trust (WOT) implementation. + *

    + * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.key.PgpKeyRegistry PgpKeyRegistry}. + */ +package org.bouncycastle.openpgp.wot.key; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java index 2bf5882815..65e9676929 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java @@ -1,6 +1,6 @@ /** * OpenPGP Web Of Trust (WOT) implementation. *

    - * The most important class and entry point is the TrustDb. + * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.TrustDb TrustDb}. */ package org.bouncycastle.openpgp.wot; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java index 2fe2978852..14ae081d76 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java @@ -1,6 +1,6 @@ package org.bouncycastle.openpgp.wot; -import static org.bouncycastle.openpgp.wot.Util.*; +import static org.bouncycastle.openpgp.wot.internal.Util.*; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java index c46df6faef..95d648bc89 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java @@ -10,7 +10,10 @@ import java.util.List; import java.util.Map; -import org.bouncycastle.openpgp.wot.TrustRecord.Trust; +import org.bouncycastle.openpgp.wot.internal.TrustDbIo; +import org.bouncycastle.openpgp.wot.internal.TrustRecord; +import org.bouncycastle.openpgp.wot.internal.TrustRecord.Trust; +import org.bouncycastle.openpgp.wot.internal.TrustRecordType; import org.bouncycastle.openpgp.wot.key.PgpKey; import org.bouncycastle.openpgp.wot.key.PgpKeyId; import org.bouncycastle.openpgp.wot.key.PgpUserId; @@ -170,7 +173,7 @@ private Map getPgpUserId2Validity() throws Exception { for (PgpKey pgpKey : pgpKeyRegistry.getMasterKeys()) { for (PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { // if (pgpUserId.getUserId() != null) { - int validity = trustDb.getValidity(pgpKey.getPublicKey(), pgpUserId.getNameHash()); + int validity = trustDb.getValidityRaw(pgpKey.getPublicKey(), pgpUserId.getNameHash()); pgpUserId2Validity.put(pgpUserId, validity); // } } diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java index f167622db4..2fbe37d91a 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java @@ -18,24 +18,24 @@ public void directOnly() throws Exception { bobKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, bobKey); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); } if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } @@ -59,36 +59,36 @@ public void oneIndirection() throws Exception { hansKey = signPublicKey(bobKey, CASUAL_CERTIFICATION, hansKey); // hans <= bob <= alice try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); - trustDb.setOwnerTrust(bobKey.getPublicKey(), TRUST_FULLY); - trustDb.setOwnerTrust(cathrinKey.getPublicKey(), TRUST_FULLY); - trustDb.setOwnerTrust(frankKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(bobKey.getPublicKey(), OwnerTrust.FULLY); + trustDb.setOwnerTrust(cathrinKey.getPublicKey(), OwnerTrust.FULLY); + trustDb.setOwnerTrust(frankKey.getPublicKey(), OwnerTrust.MARGINAL); } if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // the signature type (CASUAL) has no effect :-( + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // the signature type (CASUAL) has no effect :-( } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // behave like GnuPG! + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // behave like GnuPG! } } @@ -121,51 +121,51 @@ public void twoIndirections() throws Exception { johnKey = signPublicKey(frankKey, POSITIVE_CERTIFICATION, johnKey); // john <= frank <= bob <= alice try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); - trustDb.setOwnerTrust(bobKey.getPublicKey(), TRUST_MARGINAL); - trustDb.setOwnerTrust(cathrinKey.getPublicKey(), TRUST_MARGINAL); - trustDb.setOwnerTrust(danielKey.getPublicKey(), TRUST_MARGINAL); - trustDb.setOwnerTrust(emilKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(bobKey.getPublicKey(), OwnerTrust.MARGINAL); + trustDb.setOwnerTrust(cathrinKey.getPublicKey(), OwnerTrust.MARGINAL); + trustDb.setOwnerTrust(danielKey.getPublicKey(), OwnerTrust.MARGINAL); + trustDb.setOwnerTrust(emilKey.getPublicKey(), OwnerTrust.MARGINAL); - trustDb.setOwnerTrust(frankKey.getPublicKey(), TRUST_MARGINAL); - trustDb.setOwnerTrust(georgKey.getPublicKey(), TRUST_MARGINAL); - trustDb.setOwnerTrust(hansKey.getPublicKey(), TRUST_MARGINAL); - trustDb.setOwnerTrust(idaKey.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(frankKey.getPublicKey(), OwnerTrust.MARGINAL); + trustDb.setOwnerTrust(georgKey.getPublicKey(), OwnerTrust.MARGINAL); + trustDb.setOwnerTrust(hansKey.getPublicKey(), OwnerTrust.MARGINAL); + trustDb.setOwnerTrust(idaKey.getPublicKey(), OwnerTrust.MARGINAL); } if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } frankKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, frankKey); // frank <= bob+cathrin <= alice @@ -180,24 +180,24 @@ public void twoIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } @@ -210,24 +210,24 @@ public void twoIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); } hansKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, hansKey); // hans <= cathrin+daniel+emil <= alice @@ -240,24 +240,24 @@ public void twoIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); } } @@ -306,16 +306,16 @@ public void threeIndirections() throws Exception { signPublicKey(signingKey, POSITIVE_CERTIFICATION, karlKey); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - trustDb.setOwnerTrust(aliceKey.getPublicKey(), TRUST_ULTIMATE); + trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); for (PgpKey key : level1Keys) - trustDb.setOwnerTrust(key.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(key.getPublicKey(), OwnerTrust.MARGINAL); for (PgpKey key : level2Keys) - trustDb.setOwnerTrust(key.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(key.getPublicKey(), OwnerTrust.MARGINAL); for (PgpKey key : level3Keys) - trustDb.setOwnerTrust(key.getPublicKey(), TRUST_MARGINAL); + trustDb.setOwnerTrust(key.getPublicKey(), OwnerTrust.MARGINAL); } @@ -323,42 +323,42 @@ public void threeIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); + assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); } @@ -369,42 +369,42 @@ public void threeIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); } } try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidity(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); + assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidity(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidity(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); } } } From 96da5a9427f4d51926252d4be92337a5116399fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Fri, 18 Sep 2015 21:25:51 +0700 Subject: [PATCH 03/17] Copied LICENSE and added missing package-info.java. --- pgwot/LICENSE | 24 +++++++++++++++++++ .../openpgp/wot/internal/package-info.java | 4 ++++ 2 files changed, 28 insertions(+) create mode 100644 pgwot/LICENSE create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/package-info.java diff --git a/pgwot/LICENSE b/pgwot/LICENSE new file mode 100644 index 0000000000..ea7fb12024 --- /dev/null +++ b/pgwot/LICENSE @@ -0,0 +1,24 @@ +Please note this should be read in the same way as the MIT license (http://opensource.org/licenses/MIT). + +*************** +*** License *** +*************** + +Copyright (c) 2000 - 2015 + * The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + * CodeWizards GmbH (http://codewizards.co) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/package-info.java new file mode 100644 index 0000000000..3f54c98afe --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/package-info.java @@ -0,0 +1,4 @@ +/** + * Internal classes used by or even constituting the OpenPGP Web Of Trust (WOT) implementation. + */ +package org.bouncycastle.openpgp.wot.internal; From 58ec5e58a208c14da641eafe09a872f8971c3c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Fri, 18 Sep 2015 22:06:56 +0700 Subject: [PATCH 04/17] Formatted code to match BC's styles: * spaces instead of tabs as indents (even though this is a bad style) * braces in next line. --- .../org/bouncycastle/openpgp/wot/Config.java | 111 +- .../bouncycastle/openpgp/wot/OwnerTrust.java | 142 +- .../bouncycastle/openpgp/wot/TrustConst.java | 68 +- .../org/bouncycastle/openpgp/wot/TrustDb.java | 1829 +++++++++-------- .../openpgp/wot/TrustDbException.java | 31 +- .../openpgp/wot/TrustDbIoException.java | 32 +- .../bouncycastle/openpgp/wot/TrustModel.java | 124 +- .../bouncycastle/openpgp/wot/Validity.java | 169 +- .../openpgp/wot/internal/PgpKeyTrust.java | 66 +- .../openpgp/wot/internal/PgpUserIdTrust.java | 134 +- .../openpgp/wot/internal/TrustDbIo.java | 1406 +++++++------ .../openpgp/wot/internal/TrustRecord.java | 994 ++++----- .../openpgp/wot/internal/TrustRecordType.java | 142 +- .../openpgp/wot/internal/Util.java | 279 +-- .../bouncycastle/openpgp/wot/key/PgpKey.java | 297 +-- .../openpgp/wot/key/PgpKeyFingerprint.java | 192 +- .../openpgp/wot/key/PgpKeyId.java | 146 +- .../openpgp/wot/key/PgpKeyRegistry.java | 1014 ++++----- .../openpgp/wot/key/PgpUserId.java | 102 +- .../openpgp/wot/key/PgpUserIdNameHash.java | 305 +-- .../openpgp/wot/package-info.java | 1 + 21 files changed, 4079 insertions(+), 3505 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java index cb27fa3799..816b4fa95c 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java @@ -5,52 +5,67 @@ * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class Config implements TrustConst { - - public static final int TM_CLASSIC = 0; - public static final int TM_PGP = 1; - public static final int TM_EXTERNAL = 2; - public static final int TM_ALWAYS = 3; - public static final int TM_DIRECT = 4; - - private static final Config instance = new Config(); - - protected Config() { - } - - public static Config getInstance() { - return instance; - } - - public short getMarginalsNeeded() { - return 3; - } - - public short getCompletesNeeded() { - return 1; - } - - public short getMaxCertDepth() { - return 5; - } - - public short getTrustModel() { - return TM_PGP; // This must never be anything else! We support only TM_PGP = 1!!! - } - - public short getMinCertLevel() { - return 2; - } - - public String getTrustModelAsString() { - switch (getTrustModel()) { - case TM_CLASSIC: return "classic"; - case TM_PGP: return "PGP"; - case TM_EXTERNAL: return "external"; - case TM_ALWAYS: return "always"; - case TM_DIRECT: return "direct"; - default: - return "unknown[" + getTrustModel() + "]"; - } - } +public class Config implements TrustConst +{ + public static final int TM_CLASSIC = 0; + public static final int TM_PGP = 1; + public static final int TM_EXTERNAL = 2; + public static final int TM_ALWAYS = 3; + public static final int TM_DIRECT = 4; + + private static final Config instance = new Config(); + + protected Config() + { + } + + public static Config getInstance() + { + return instance; + } + + public short getMarginalsNeeded() + { + return 3; + } + + public short getCompletesNeeded() + { + return 1; + } + + public short getMaxCertDepth() + { + return 5; + } + + public short getTrustModel() + { + return TM_PGP; // This must never be anything else! We support only + // TM_PGP = 1!!! + } + + public short getMinCertLevel() + { + return 2; + } + + public String getTrustModelAsString() + { + switch (getTrustModel()) + { + case TM_CLASSIC: + return "classic"; + case TM_PGP: + return "PGP"; + case TM_EXTERNAL: + return "external"; + case TM_ALWAYS: + return "always"; + case TM_DIRECT: + return "direct"; + default: + return "unknown[" + getTrustModel() + "]"; + } + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java index 1659c2dc16..56a9ef31d4 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java @@ -4,95 +4,99 @@ import java.util.Map; /** - * The owner-trust is assigned to a key, but does not describe the key itself. - * It specifies how reliable the owner of this key is in his function as notary - * certifying other keys. + * The owner-trust is assigned to a key, but does not describe the key itself. It specifies how reliable the + * owner of this key is in his function as notary certifying other keys. *

    * When judging the owner-trust of a person, you should answer the following questions: *

      *
    • How well does this person understand OpenPGP? - *
    • How well does this person protect his computer's integrity and most importantly - * his private key? - *
    • How well does this person check the authenticity of another key before signing - * (= certifying) it? + *
    • How well does this person protect his computer's integrity and most importantly his private key? + *
    • How well does this person check the authenticity of another key before signing (= certifying) it? *
    *

    - * Whether you trust this person on a personal level, should have only a minor influence - * on the owner-trust. For example, you certainly trust your mother, but unless she's - * really computer-savvy, you likely assign a low owner-trust to her key. + * Whether you trust this person on a personal level, should have only a minor influence on the owner-trust. For + * example, you certainly trust your mother, but unless she's really computer-savvy, you likely assign a low owner-trust + * to her key. * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co * @see TrustDb#getOwnerTrust(org.bouncycastle.openpgp.wot.key.PgpKey) */ -public enum OwnerTrust { +public enum OwnerTrust +{ + /** + * It is unknown, how reliable the key's owner is as notary for other keys. + *

    + * This causes no transitive trust. + */ + UNKNOWN(TrustConst.TRUST_UNKNOWN), // 0 - /** - * It is unknown, how reliable the key's owner is as notary for other keys. - *

    - * This causes no transitive trust. - */ - UNKNOWN(TrustConst.TRUST_UNKNOWN), // 0 + /** + * The key's owner should not be trusted. + *

    + * This causes no transitive trust. + */ + NEVER(TrustConst.TRUST_NEVER), // 3 - /** - * The key's owner should not be trusted. - *

    - * This causes no transitive trust. - */ - NEVER(TrustConst.TRUST_NEVER), // 3 + /** + * The key's owner is a marginally trustable notary. Certifications of keys made by this key's owner can thus be + * trusted a little. + *

    + * This causes some transitive trust. Together with other "marginally" trusted owner's certifications, it might + * cause a key to be trusted fully. + */ + MARGINAL(TrustConst.TRUST_MARGINAL), // 4 - /** - * The key's owner is a marginally trustable notary. Certifications of keys made by this key's owner can thus be trusted a little. - *

    - * This causes some transitive trust. Together with other "marginally" trusted owner's - * certifications, it might cause a key to be trusted fully. - */ - MARGINAL(TrustConst.TRUST_MARGINAL), // 4 + /** + * The key's owner is a fully trusted notary. Certificaton of keys made by this key's owner are thus considered very + * reliable. + *

    + * This causes significant transitive trust. Depending on the settings, this is already enough for a certified key + * to be trusted fully, or it might require further signatures. + */ + FULLY(TrustConst.TRUST_FULLY), // 5 - /** - * The key's owner is a fully trusted notary. Certificaton of keys made by this key's owner are thus considered very reliable. - *

    - * This causes significant transitive trust. Depending on the settings, this is already - * enough for a certified key to be trusted fully, or it might require further signatures. - */ - FULLY(TrustConst.TRUST_FULLY), // 5 + /** + * The key's owner can be ultimately trusted. One single signature of a notary whose key is marked 'ultimate' is + * sufficient for full transitive trust. + *

    + * Usually, the user himself marks all his own keys with this owner-trust. + */ + ULTIMATE(TrustConst.TRUST_ULTIMATE) // 6 + ; - /** - * The key's owner can be ultimately trusted. One single signature of a notary whose key is marked 'ultimate' is sufficient - * for full transitive trust. - *

    - * Usually, the user himself marks all his own keys with this owner-trust. - */ - ULTIMATE(TrustConst.TRUST_ULTIMATE) // 6 - ; + private final int numericValue; - private final int numericValue; + private static Map numericValue2OwnerTrust; - private static Map numericValue2OwnerTrust; + private OwnerTrust(final int numericValue) + { + this.numericValue = numericValue; + } - private OwnerTrust(final int numericValue) { - this.numericValue = numericValue; - } + public int getNumericValue() + { + return numericValue; + } - public int getNumericValue() { - return numericValue; - } + public static OwnerTrust fromNumericValue(final int numericValue) + { + final OwnerTrust ownerTrust = getNumericValue2OwnerTrust().get(numericValue); + if (ownerTrust == null) + throw new IllegalArgumentException("numericValue unknown: " + numericValue); - public static OwnerTrust fromNumericValue(final int numericValue) { - final OwnerTrust ownerTrust = getNumericValue2OwnerTrust().get(numericValue); - if (ownerTrust == null) - throw new IllegalArgumentException("numericValue unknown: " + numericValue); + return ownerTrust; + } - return ownerTrust; - } + private static Map getNumericValue2OwnerTrust() + { + if (numericValue2OwnerTrust == null) + { + Map m = new HashMap<>(values().length); + for (OwnerTrust ownerTrust : values()) + m.put(ownerTrust.getNumericValue(), ownerTrust); - private static Map getNumericValue2OwnerTrust() { - if (numericValue2OwnerTrust == null) { - Map m = new HashMap<>(values().length); - for (OwnerTrust ownerTrust : values()) - m.put(ownerTrust.getNumericValue(), ownerTrust); - - numericValue2OwnerTrust = m; - } - return numericValue2OwnerTrust; - } + numericValue2OwnerTrust = m; + } + return numericValue2OwnerTrust; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java index bcfd196b2d..80f6493971 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java @@ -1,39 +1,39 @@ package org.bouncycastle.openpgp.wot; -public interface TrustConst { - int TRUST_RECORD_LEN = 40; - int SIGS_PER_RECORD = (TRUST_RECORD_LEN - 10) / 5; - int ITEMS_PER_HTBL_RECORD = (TRUST_RECORD_LEN - 2) / 4; - int ITEMS_PER_HLST_RECORD = (TRUST_RECORD_LEN - 6) / 5; - int ITEMS_PER_PREF_RECORD = TRUST_RECORD_LEN - 10; - int MAX_LIST_SIGS_DEPTH = 20; - int MAX_CACHE_SIZE = 1024 * 1024; +public interface TrustConst +{ + int TRUST_RECORD_LEN = 40; + int SIGS_PER_RECORD = (TRUST_RECORD_LEN - 10) / 5; + int ITEMS_PER_HTBL_RECORD = (TRUST_RECORD_LEN - 2) / 4; + int ITEMS_PER_HLST_RECORD = (TRUST_RECORD_LEN - 6) / 5; + int ITEMS_PER_PREF_RECORD = TRUST_RECORD_LEN - 10; + int MAX_LIST_SIGS_DEPTH = 20; + int MAX_CACHE_SIZE = 1024 * 1024; + int TRUST_MASK = 15; + /** o: not yet calculated/assigned */ + int TRUST_UNKNOWN = 0; + // /** e: calculation may be invalid */ + // int TRUST_EXPIRED = 1; // unused?! gnupg seems to never assign this value... + /** q: not enough information for calculation */ + int TRUST_UNDEFINED = 2; + /** n: never trust this pubkey */ + int TRUST_NEVER = 3; + /** m: marginally trusted */ + int TRUST_MARGINAL = 4; + /** f: fully trusted */ + int TRUST_FULLY = 5; + /** u: ultimately trusted */ + int TRUST_ULTIMATE = 6; - int TRUST_MASK = 15; - /** o: not yet calculated/assigned */ - int TRUST_UNKNOWN = 0; -// /** e: calculation may be invalid */ -// int TRUST_EXPIRED = 1; // unused?! gnupg seems to never assign this value... - /** q: not enough information for calculation */ - int TRUST_UNDEFINED = 2; - /** n: never trust this pubkey */ - int TRUST_NEVER = 3; - /** m: marginally trusted */ - int TRUST_MARGINAL = 4; - /** f: fully trusted */ - int TRUST_FULLY = 5; - /** u: ultimately trusted */ - int TRUST_ULTIMATE = 6; - - // BEGIN trust values not covered by the mask - /** r: revoked */ - int TRUST_FLAG_REVOKED = 32; - /** r: revoked but for subkeys */ - int TRUST_FLAG_SUB_REVOKED = 64; - /** d: key/uid disabled */ - int TRUST_FLAG_DISABLED = 128; - /** a check-trustdb is pending */ - int TRUST_FLAG_PENDING_CHECK = 256; - // END trust values not covered by the mask + // BEGIN trust values not covered by the mask + /** r: revoked */ + int TRUST_FLAG_REVOKED = 32; + /** r: revoked but for subkeys */ + int TRUST_FLAG_SUB_REVOKED = 64; + /** d: key/uid disabled */ + int TRUST_FLAG_DISABLED = 128; + /** a check-trustdb is pending */ + int TRUST_FLAG_PENDING_CHECK = 256; + // END trust values not covered by the mask } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java index da130c1542..d3be2b3976 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java @@ -44,857 +44,984 @@ *

*

* This class was mostly ported from the GnuPG's {@code trustdb.h} and {@code trustdb.c} files. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class TrustDb implements AutoCloseable, TrustConst { - private static final Logger logger = LoggerFactory.getLogger(TrustDb.class); - - private final TrustDbIo trustDbIo; - private final PgpKeyRegistry pgpKeyRegistry; - - private long startTime; - private long nextExpire; - private Map fingerprint2PgpKeyTrust; - private Set klist; - private Set fullTrust; - private DateFormat dateFormatIso8601WithTime; - - /** - * Create a {@code TrustDb} instance with the given {@code trustdb.gpg} file and the given key-registry. - *

- * Important: You must {@linkplain #close() close} this instance! - * @param file the trust-database-file ({@code trustdb.gpg}). Must not be null. - * @param pgpKeyRegistry the key-registry. Must not be null. - */ - public TrustDb(final File file, final PgpKeyRegistry pgpKeyRegistry) { - assertNotNull("file", file); - this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); - this.trustDbIo = new TrustDbIo(file); - } - - @Override - public void close() throws Exception { - trustDbIo.close(); - } - - public DateFormat getDateFormatIso8601WithTime() { - if (dateFormatIso8601WithTime == null) - dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - - return dateFormatIso8601WithTime; - } - - protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) { - PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); - if (pgpKeyTrust == null) { - pgpKeyTrust = new PgpKeyTrust(pgpKey); - fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); - } - return pgpKeyTrust; - } - - // reset_trust_records(void) - protected void resetTrustRecords() { - TrustRecord record; - long recordNum = 0; - int count = 0, nreset = 0; - - while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) { - if (record.getType() == TrustRecordType.TRUST) { - final TrustRecord.Trust trust = (TrustRecord.Trust) record; - ++count; - if (trust.getMinOwnerTrust() != 0) { - trust.setMinOwnerTrust((short) 0); - trustDbIo.putTrustRecord(record); - } - } - else if (record.getType() == TrustRecordType.VALID) { - final TrustRecord.Valid valid = (TrustRecord.Valid) record; - if (((valid.getValidity() & TRUST_MASK) != 0) - || valid.getMarginalCount() != 0 - || valid.getFullCount() != 0) { - - valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); - valid.setMarginalCount((short) 0); - valid.setFullCount((short) 0); - nreset++; - trustDbIo.putTrustRecord(record); - } - } - } - - logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); - } - - /** - * Gets the assigned owner-trust value for the given public key. - *

- * This value specifies how much the user trusts the owner of the given key in his function as notary - * certifying other keys. - * @param pgpKey the key whose owner-trust should be looked up. Must not be null. - * @return the owner-trust. Never null. If none has been assigned, before, this - * method returns {@link OwnerTrust#UNKNOWN UNKNOWN}. - * @see #setOwnerTrust(PgpKey, OwnerTrust) - * @see #getOwnerTrust(PGPPublicKey) - */ - public OwnerTrust getOwnerTrust(PgpKey pgpKey) { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - return getOwnerTrust(pgpKey.getPublicKey()); - } - - /** - * Sets the given key's owner-trust. - *

- * This value specifies how much the user trusts the owner of the given key in his function as notary - * certifying other keys. - *

- * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. - * @param pgpKey the key whose owner-trust is to be set. Must not be null. - * @param ownerTrust the owner-trust to be assigned. Must not be null. - * @see #getOwnerTrust(PgpKey) - * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) - */ - public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) { - assertNotNull("pgpKey", pgpKey); - assertNotNull("ownerTrust", ownerTrust); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); - } - - /** - * Gets the assigned owner-trust value for the given public key. - *

- * This value specifies how much the user trusts the owner of the given key in his function as notary - * certifying other keys. - *

- * The given key should be a master key. - * @param publicKey the key whose owner-trust should be looked up. Must not be null. - * @return the owner-trust. Never null. If none has been assigned, before, this - * method returns {@link OwnerTrust#UNKNOWN UNKNOWN}. - * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) - * @see #getOwnerTrust(PgpKey) - */ - public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) { - assertNotNull("publicKey", publicKey); -// if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) -// return TRUST_UNKNOWN; // TODO maybe we should support other trust models... - - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - return OwnerTrust.UNKNOWN; - - return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); - } - - /** - * Sets the given key's owner-trust. - *

- * This value specifies how much the user trusts the owner of the given key in his function as notary - * certifying other keys. - *

- * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. - *

- * The given key should be a master key. - * @param publicKey the key whose owner-trust is to be set. Must not be null. - * @param ownerTrust the owner-trust to be assigned. Must not be null. - * @see #getOwnerTrust(PGPPublicKey) - * @see #setOwnerTrust(PgpKey, OwnerTrust) - */ - public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) { - assertNotNull("publicKey", publicKey); - assertNotNull("ownerTrust", ownerTrust); - - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) { - // No record yet - create a new one. - trust = new TrustRecord.Trust(); - trust.setFingerprint(publicKey.getFingerprint()); - } - - int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; - - trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); - trustDbIo.putTrustRecord(trust); - - markTrustDbStale(); - trustDbIo.flush(); - } - - protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); - return trust; - } - - /** - * Gets the validity of the given key. - *

- * The validity of a key is the highest validity of all its user-identities (and -attributes). It can be one of - * {@link Validity}'s numeric values (see also the {@link TrustConst} constants) and - * it additionally contains the following bit flags: - *

    - *
  • {@link TrustConst#TRUST_FLAG_DISABLED} - corresponds to {@link #isDisabled(PGPPublicKey)}. - *
  • {@link TrustConst#TRUST_FLAG_REVOKED} - corresponds to {@link PGPPublicKey#hasRevocation()}. - *
  • {@link TrustConst#TRUST_FLAG_PENDING_CHECK} - corresponds to {@link #isTrustDbStale()}. - *
- *

- * This method does not calculate the validity! It does solely look it up in the trust-database. - * The validity is (re)calculated by {@link #updateTrustDb()}. - * @param publicKey the key whose validity is to be returned. Must not be null. - * @return the validity with bit flags. - * @see #getValidityRaw(PGPPublicKey, PgpUserIdNameHash) - * @deprecated This method exists for compatibility with GnuPG and for easier comparisons between - * GnuPG's calculations and the calculations of this code. Do not use it in your code! - * Use {@link #getValidity(PGPPublicKey)} instead. - */ - @Deprecated - public synchronized int getValidityRaw(final PGPPublicKey publicKey) { - assertNotNull("publicKey", publicKey); - return _getValidity(publicKey, (PgpUserIdNameHash) null, true); - } - - /** - * Gets the validity of the given user-identity. - *

    - *
  • {@link TrustConst#TRUST_FLAG_DISABLED} - corresponds to {@link #isDisabled(PGPPublicKey)}. - *
  • {@link TrustConst#TRUST_FLAG_REVOKED} - corresponds to {@link PGPPublicKey#hasRevocation()}. - *
  • {@link TrustConst#TRUST_FLAG_PENDING_CHECK} - corresponds to {@link #isTrustDbStale()}. - *
- *

- * This method does not calculate the validity! It does solely look it up in the trust-database. - * The validity is (re)calculated by {@link #updateTrustDb()}. - * @param publicKey the key whose validity is to be returned. Must not be null. - * @param pgpUserIdNameHash user-id's (or user-attribute's) name-hash. Must not be null. - * @return the validity with bit flags. - * @see #getValidityRaw(PGPPublicKey) - * @deprecated This method exists for compatibility with GnuPG and for easier comparisons between - * GnuPG's calculations and the calculations of this code. Do not use it in your code! - * Use {@link #getValidity(PGPPublicKey, PgpUserIdNameHash)} instead. - */ - @Deprecated - public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) { - assertNotNull("publicKey", publicKey); - assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); - return _getValidity(publicKey, pgpUserIdNameHash, true); - } - - /** - * Gets the validity of the given key. - *

- * The validity of a key is the highest validity of all its user-identities (and -attributes). - *

- * This method does not calculate the validity! It does solely look it up in the trust-database. - * The validity is (re)calculated by {@link #updateTrustDb()}. - * @param pgpKey the key whose validity to look up. Must not be null. - * @return the validity of the given {@code publicKey}. Never null. - * @see #getValidity(PgpKey, PgpUserIdNameHash) - * @see #getValidity(PGPPublicKey) - */ - public Validity getValidity(final PgpKey pgpKey) { - assertNotNull("pgpKey", pgpKey); - return getValidity(pgpKey.getPublicKey()); - } - - /** - * Gets the validity of the given user-identity (or -attribute). - *

- * This method does not calculate the validity! It does solely look it up in the trust-database. - * The validity is (re)calculated by {@link #updateTrustDb()}. - * @param pgpUserId the user-identity (or -attribute) whose validity to - * look up. Must not be null. - * @return the validity of the given user-identity. Never null. - * @see #getValidity(PgpKey) - * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) - */ - public Validity getValidity(final PgpUserId pgpUserId) { - assertNotNull("pgpUserId", pgpUserId); - return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); - } - - /** - * Gets the validity of the given key. - *

- * The validity of a key is the highest validity of all its user-identities (and -attributes). - *

- * This method does not calculate the validity! It does solely look it up in the trust-database. - * The validity is (re)calculated by {@link #updateTrustDb()}. - * @param publicKey the key whose validity to look up. Must not be null. - * @return the validity of the given {@code publicKey}. Never null. - * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) - */ - public Validity getValidity(final PGPPublicKey publicKey) { - assertNotNull("publicKey", publicKey); - final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); - return Validity.fromNumericValue(numericValue); - } - - /** - * Gets the validity of the given user-identity (or -attribute). - *

- * This method does not calculate the validity! It does solely look it up in the trust-database. - * The validity is (re)calculated by {@link #updateTrustDb()}. - * @param publicKey the key whose validity to look up. Must not be null. - * @param pgpUserIdNameHash the name-hash of the user-identity (or -attribute) whose validity to - * look up. Must not be null. - * @return the validity of the given user-identity. Never null. - * @see #getValidity(PGPPublicKey) - */ - public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) { - assertNotNull("publicKey", publicKey); - assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); - final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); - return Validity.fromNumericValue(numericValue); - } - - /** - * Ported from {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} - */ - protected synchronized int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, final boolean withFlags) { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - return TRUST_UNKNOWN; - - // Loop over all user IDs - long recordNum = trust.getValidList(); - int validity = 0; - int flags = 0; - // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust - // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, - // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). - while (recordNum != 0) { - TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); - assertNotNull("valid", valid); - - if (pgpUserIdNameHash != null) { - // If a user ID is given we return the validity for that - // user ID ONLY. If the namehash is not found, then there - // is no validity at all (i.e. the user ID wasn't signed). - if (pgpUserIdNameHash.equals(valid.getNameHash())) { - validity = valid.getValidity() & TRUST_MASK; - flags = valid.getValidity() & ~TRUST_MASK; - break; - } - } - else { - // If no user ID is given, we take the maximum validity over all user IDs - validity = Math.max(validity, valid.getValidity() & TRUST_MASK); - flags |= valid.getValidity() & ~TRUST_MASK; - } - recordNum = valid.getNext(); - } - - if (withFlags) { - validity |= flags; - - if ( (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0 ) - validity |= TRUST_FLAG_DISABLED; - - if (publicKey.hasRevocation()) - validity |= TRUST_FLAG_REVOKED; - - if (isTrustDbStale()) - validity |= TRUST_FLAG_PENDING_CHECK; - } - return validity; - } - - // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) - protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) { - assertNotNull("pgpUserId", pgpUserId); - assertNonNegativeShort("depth", depth); - assertNonNegativeShort("validity", validity); - assertNonNegativeShort("fullCount", fullCount); - assertNonNegativeShort("marginalCount", marginalCount); - - TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); - if (trust == null) { - // No record yet - create a new one. - trust = new TrustRecord.Trust(); - trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); - trustDbIo.putTrustRecord(trust); - } - - TrustRecord.Valid valid = null; - - // locate an existing Valid record - final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); - long recordNum = trust.getValidList(); - while (recordNum != 0) { - valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); - if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) - break; - - recordNum = valid.getNext(); - } - - if (recordNum == 0) { // insert a new validity record - valid = new TrustRecord.Valid(); - valid.setNameHash(pgpUserIdNameHashBytes); - valid.setNext(trust.getValidList()); - trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record - trust.setValidList(valid.getRecordNum()); - } - - valid.setValidity((short) validity); - valid.setFullCount((short) fullCount); - valid.setMarginalCount((short) marginalCount); - trust.setDepth((short) depth); - trustDbIo.putTrustRecord(trust); - trustDbIo.putTrustRecord(valid); - } - - private static void assertNonNegativeShort(final String name, final int value) { - assertNotNull("name", name); - - if (value < 0) - throw new IllegalArgumentException(name + " < 0"); - - if (value > Short.MAX_VALUE) - throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); - } - - /** - * Marks all those keys that we have a secret key for as ultimately trusted. If we have a secret/private key, - * we assume it to be *our* key and we always trust ourselves. - * @param onlyIfMissing whether only those keys' owner-trust should be set which do not yet have - * an owner-trust assigned. - */ - public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) { - for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) { - if (masterKey.getSecretKey() == null) - continue; - - TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); - if (trust == null - || trust.getOwnerTrust() == TRUST_UNKNOWN - || !onlyIfMissing) { - - if (trust == null) { - trust = new TrustRecord.Trust(); - trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); - } - - trust.setDepth((short) 0); - trust.setOwnerTrust((short) TRUST_ULTIMATE); - trustDbIo.putTrustRecord(trust); - } - } - } - - protected Set getUltimatelyTrustedKeyFingerprints() { - Set result = new HashSet(); - TrustRecord record; - long recordNum = 0; - while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) { - if (record.getType() == TrustRecordType.TRUST) { - TrustRecord.Trust trust = (TrustRecord.Trust) record; - if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) - result.add(new PgpKeyFingerprint(trust.getFingerprint())); - } - } - return result; - } - - public boolean isExpired(PGPPublicKey publicKey) { - assertNotNull("publicKey", publicKey); - - final Date creationTime = publicKey.getCreationTime(); - - final long validSeconds = publicKey.getValidSeconds(); - if (validSeconds != 0) { - long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); - return validUntilTimestamp < System.currentTimeMillis(); - } - return false; - // TODO there seem to be keys (very old keys) that seem to encode the validity differently. - // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it - // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. - // It's a very small number of keys only, hence I ignore it for now ;-) - } - - /** - * Determines whether the specified key is marked as disabled. - * @param pgpKey the key whose status to query. Must not be null. - * @return true, if the key is marked as disabled; false, if the key is enabled. - */ - public boolean isDisabled(PgpKey pgpKey) { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - return isDisabled(pgpKey.getPublicKey()); - } - - /** - * Enables or disabled the specified key. - * @param pgpKey the key whose status to query. Must not be null. - * @param disabled true to disable the key; false to enable it. - */ - public void setDisabled(PgpKey pgpKey, final boolean disabled) { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - setDisabled(pgpKey.getPublicKey(), disabled); - } - - /** - * Determines whether the specified key is marked as disabled. - *

- * The key should be a master-key. - * @param publicKey the key whose status to query. Must not be null. - * This should be a master-key. - * @return true, if the key is marked as disabled; false, if the key is enabled. - */ - public boolean isDisabled(final PGPPublicKey publicKey) { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); - if (trust == null) - return false; - - return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; - } - - /** - * Enables or disabled the specified key. - *

- * The key should be a master-key. - * @param publicKey the key whose status to query. Must not be null. - * This should be a master-key. - * @param disabled true to disable the key; false to enable it. - */ - public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); - if (trust == null) { - trust = new TrustRecord.Trust(); - trust.setFingerprint(publicKey.getFingerprint()); - } - - int ownerTrust = trust.getOwnerTrust(); - if (disabled) - ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; - else - ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; - - trust.setOwnerTrust((short) ownerTrust); - - trustDbIo.putTrustRecord(trust); - trustDbIo.flush(); - } - - /** - * Determines if the trust-database is stale. It becomes stale, if it is either - * explicitly {@linkplain #markTrustDbStale() marked stale} or if a key expires. - *

- * Important: It does not become stale when a key ring file is modified! Thus, when adding new - * keys, {@link #markTrustDbStale()} or {@link #updateTrustDb()} must be invoked. - * @return true, if the trust-database is stale; false, if it is up-to-date. - * @see #markTrustDbStale() - * @see #updateTrustDb() - * @see #updateTrustDbIfNeeded() - */ - public synchronized boolean isTrustDbStale() { - final Config config = Config.getInstance(); - final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - if (config.getTrustModel() != version.getTrustModel()) { - TrustModel configTrustModel; - try { - configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); - } catch (IllegalArgumentException x) { - configTrustModel = null; - } - - TrustModel versionTrustModel; - try { - versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); - } catch (IllegalArgumentException x) { - versionTrustModel = null; - } - - logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", - config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); - - return true; - } - - if (config.getCompletesNeeded() != version.getCompletesNeeded()) { - logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", - config.getCompletesNeeded(), version.getCompletesNeeded()); - - return true; - } - - if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) { - logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", - config.getMarginalsNeeded(), version.getMarginalsNeeded()); - - return true; - } - - if (config.getMaxCertDepth() != version.getCertDepth()) { - logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", - config.getMaxCertDepth(), version.getCertDepth()); - - return true; - } - - final Date now = new Date(); - if (version.getNextCheck().before(now)) { - logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", - getDateFormatIso8601WithTime().format(version.getNextCheck()), - getDateFormatIso8601WithTime().format(now)); - - return true; - } - - logger.trace("isTrustDbStale: stale=false"); - return false; - } - - /** - * Marks the trust-db as being stale. - *

- * Either this method or {@link #updateTrustDb()} must be invoked whenever a new key was added - * to the key ring, because the WOT-related code does not keep track of key-ring-changes - * ({@link #isTrustDbStale()} does not detect them). - * @see #isTrustDbStale() - * @see #updateTrustDb() - */ - public synchronized void markTrustDbStale() { - final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - version.setNextCheck(new Date(0)); - trustDbIo.putTrustRecord(version); - } - - /** - * Update the {@code trustdb.gpg} by recalculating all keys' validities, if it is needed. - * An update is needed, if the {@linkplain #isTrustDbStale() trust-db is stale}. - * @see #updateTrustDb() - * @see #isTrustDbStale() - */ - public synchronized void updateTrustDbIfNeeded() { - if (isTrustDbStale()) - updateTrustDb(); - } - - /** - * Update the {@code trustdb.gpg} by recalculating all keys' validities. - *

- * Either this method or {@link #markTrustDbStale()} must be invoked whenever a new key was added - * to the key ring, because the WOT-related code does not keep track of key-ring-changes - * ({@link #isTrustDbStale()} does not detect them). - *

- * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, - * because the implementation looked overly complicated. This method here is a re-implementation - * from scratch. It still seems to come very closely to the behaviour of GnuPG's original code. - * @see #updateTrustDbIfNeeded() - */ - public synchronized void updateTrustDb() { - final Config config = Config.getInstance(); - try { - fingerprint2PgpKeyTrust = new HashMap<>(); - fullTrust = new HashSet<>(); - - startTime = System.currentTimeMillis() / 1000; - nextExpire = Long.MAX_VALUE; - - resetTrustRecords(); - - final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); - if (ultimatelyTrustedKeyFingerprints.isEmpty()) { - logger.warn("updateTrustDb: There are no ultimately trusted keys!"); - return; - } - - // mark all UTKs as used and fully_trusted and set validity to ultimate - for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) { - final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); - if (utk == null) { - logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); - continue; - } - - fullTrust.add(utkFpr); - - for (PgpUserId pgpUserId : utk.getPgpUserIds()) - updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); - - final long expireDate = getExpireTimestamp(utk.getPublicKey()); - if (expireDate >= startTime && expireDate < nextExpire) - nextExpire = expireDate; - } - - klist = ultimatelyTrustedKeyFingerprints; - - for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) { - final List validatedKeys = validateKeyList(); - - klist = new HashSet<>(); - for (PgpKey pgpKey : validatedKeys) { - PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); - klist.add(pgpKey.getPgpKeyFingerprint()); - - for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) { - final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); - - final int validity = pgpUserIdTrust.getValidity(); - updateValidity(pgpUserId, depth, validity, - pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); - - if (validity >= TRUST_FULLY) - fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); - } - - final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); - if (expireDate >= startTime && expireDate < nextExpire) - nextExpire = expireDate; - } - - logger.debug("updateTrustDb: depth={} keys={}", - depth, validatedKeys.size()); - } - - final Date nextExpireDate = new Date(nextExpire * 1000); - trustDbIo.updateVersionRecord(nextExpireDate); - - trustDbIo.flush(); - - logger.info("updateTrustDb: Next trust-db expiration date: {}", getDateFormatIso8601WithTime().format(nextExpireDate)); - } finally { - fingerprint2PgpKeyTrust = null; - klist = null; - fullTrust = null; - } - } - - private long getExpireTimestamp(PGPPublicKey pk) { - final long validSeconds = pk.getValidSeconds(); - if (validSeconds == 0) - return Long.MAX_VALUE; - - final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; - return result; - } - - /** - * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, - * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. - * @return the keys that were processed by this method. - */ - private List validateKeyList() { - final List result = new ArrayList<>(); - final Set signedPgpKeyFingerprints = new HashSet<>(); - for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) - signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); - - signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted - - for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) { - final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); - if (pgpKey == null) { - logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); - continue; - } - result.add(pgpKey); - validateKey(pgpKey); - } - return result; - } - - /** - * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, - * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. - * @param pgpKey the pgp-key to be validated. Must not be null. - */ - private void validateKey(final PgpKey pgpKey) { - assertNotNull("pgpKey", pgpKey); - logger.debug("validateKey: {}", pgpKey); - - final Config config = Config.getInstance(); - final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); - - final boolean expired = isExpired(pgpKey.getPublicKey()); -// final boolean disabled = isDisabled(pgpKey.getPublicKey()); - final boolean revoked = pgpKey.getPublicKey().hasRevocation(); - - for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { - final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); - - pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 - pgpUserIdTrust.setUltimateCount(0); - pgpUserIdTrust.setFullCount(0); - pgpUserIdTrust.setMarginalCount(0); - - if (expired) - continue; - -// if (disabled) -// continue; - - if (revoked) - continue; - - for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) { - // It seems, the PGP trust model does not care about the certification level :-( - // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - - // there is no difference (at least according to my tests). - if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION - && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION - && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) - continue; - - final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); - if (signingKey == null) - continue; - - final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); - if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) - && signingOwnerTrust != OwnerTrust.ULTIMATE) { - // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. - continue; - } - - int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; - if (signingValidity <= TRUST_MARGINAL) { - // If the signingKey is trusted only marginally or less, we ignore the certification completely. - // Only fully trusted keys are taken into account for transitive trust. - continue; - } - - // The owner-trust of the signing key is relevant. - switch (signingOwnerTrust) { - case ULTIMATE: - pgpUserIdTrust.incUltimateCount(); - break; - case FULLY: - pgpUserIdTrust.incFullCount(); - break; - case MARGINAL: - pgpUserIdTrust.incMarginalCount(); - break; - default: // ignoring! - break; - } - } - - if (pgpUserIdTrust.getUltimateCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_FULLY); - else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULLY); - else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULLY); - else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_MARGINAL); - } - } +public class TrustDb implements AutoCloseable, TrustConst +{ + private static final Logger logger = LoggerFactory.getLogger(TrustDb.class); + + private final TrustDbIo trustDbIo; + private final PgpKeyRegistry pgpKeyRegistry; + + private long startTime; + private long nextExpire; + private Map fingerprint2PgpKeyTrust; + private Set klist; + private Set fullTrust; + private DateFormat dateFormatIso8601WithTime; + + /** + * Create a {@code TrustDb} instance with the given {@code trustdb.gpg} file and the given key-registry. + *

+ * Important: You must {@linkplain #close() close} this instance! + * + * @param file + * the trust-database-file ({@code trustdb.gpg}). Must not be null. + * @param pgpKeyRegistry + * the key-registry. Must not be null. + */ + public TrustDb(final File file, final PgpKeyRegistry pgpKeyRegistry) + { + assertNotNull("file", file); + this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); + this.trustDbIo = new TrustDbIo(file); + } + + @Override + public void close() throws Exception + { + trustDbIo.close(); + } + + public DateFormat getDateFormatIso8601WithTime() + { + if (dateFormatIso8601WithTime == null) + dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + return dateFormatIso8601WithTime; + } + + protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) + { + PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); + if (pgpKeyTrust == null) + { + pgpKeyTrust = new PgpKeyTrust(pgpKey); + fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); + } + return pgpKeyTrust; + } + + // reset_trust_records(void) + protected void resetTrustRecords() + { + TrustRecord record; + long recordNum = 0; + int count = 0, nreset = 0; + + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) + { + if (record.getType() == TrustRecordType.TRUST) + { + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + ++count; + if (trust.getMinOwnerTrust() != 0) + { + trust.setMinOwnerTrust((short) 0); + trustDbIo.putTrustRecord(record); + } + } + else if (record.getType() == TrustRecordType.VALID) + { + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + if (((valid.getValidity() & TRUST_MASK) != 0) + || valid.getMarginalCount() != 0 + || valid.getFullCount() != 0) + { + + valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); + valid.setMarginalCount((short) 0); + valid.setFullCount((short) 0); + nreset++; + trustDbIo.putTrustRecord(record); + } + } + } + + logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); + } + + /** + * Gets the assigned owner-trust value for the given public key. + *

+ * This value specifies how much the user trusts the owner of the given key in his function as notary certifying + * other keys. + * + * @param pgpKey + * the key whose owner-trust should be looked up. Must not be null. + * @return the owner-trust. Never null. If none has been assigned, before, this method returns + * {@link OwnerTrust#UNKNOWN UNKNOWN}. + * @see #setOwnerTrust(PgpKey, OwnerTrust) + * @see #getOwnerTrust(PGPPublicKey) + */ + public OwnerTrust getOwnerTrust(PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return getOwnerTrust(pgpKey.getPublicKey()); + } + + /** + * Sets the given key's owner-trust. + *

+ * This value specifies how much the user trusts the owner of the given key in his function as notary certifying + * other keys. + *

+ * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. + * + * @param pgpKey + * the key whose owner-trust is to be set. Must not be null. + * @param ownerTrust + * the owner-trust to be assigned. Must not be null. + * @see #getOwnerTrust(PgpKey) + * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) + */ + public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) + { + assertNotNull("pgpKey", pgpKey); + assertNotNull("ownerTrust", ownerTrust); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); + } + + /** + * Gets the assigned owner-trust value for the given public key. + *

+ * This value specifies how much the user trusts the owner of the given key in his function as notary certifying + * other keys. + *

+ * The given key should be a master key. + * + * @param publicKey + * the key whose owner-trust should be looked up. Must not be null. + * @return the owner-trust. Never null. If none has been assigned, before, this method returns + * {@link OwnerTrust#UNKNOWN UNKNOWN}. + * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) + * @see #getOwnerTrust(PgpKey) + */ + public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + // if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + // return TRUST_UNKNOWN; // TODO maybe we should support other trust models... + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return OwnerTrust.UNKNOWN; + + return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); + } + + /** + * Sets the given key's owner-trust. + *

+ * This value specifies how much the user trusts the owner of the given key in his function as notary certifying + * other keys. + *

+ * The user should mark all own keys with {@link TrustConst#TRUST_ULTIMATE TRUST_ULTIMATE}. + *

+ * The given key should be a master key. + * + * @param publicKey + * the key whose owner-trust is to be set. Must not be null. + * @param ownerTrust + * the owner-trust to be assigned. Must not be null. + * @see #getOwnerTrust(PGPPublicKey) + * @see #setOwnerTrust(PgpKey, OwnerTrust) + */ + public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) + { + assertNotNull("publicKey", publicKey); + assertNotNull("ownerTrust", ownerTrust); + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; + + trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); + trustDbIo.putTrustRecord(trust); + + markTrustDbStale(); + trustDbIo.flush(); + } + + protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); + return trust; + } + + /** + * Gets the validity of the given key. + *

+ * The validity of a key is the highest validity of all its user-identities (and -attributes). It can be one of + * {@link Validity}'s numeric values (see also the {@link TrustConst} constants) and it additionally contains the + * following bit flags: + *

    + *
  • {@link TrustConst#TRUST_FLAG_DISABLED} - corresponds to {@link #isDisabled(PGPPublicKey)}. + *
  • {@link TrustConst#TRUST_FLAG_REVOKED} - corresponds to {@link PGPPublicKey#hasRevocation()}. + *
  • {@link TrustConst#TRUST_FLAG_PENDING_CHECK} - corresponds to {@link #isTrustDbStale()}. + *
+ *

+ * This method does not calculate the validity! It does solely look it up in the trust-database. The validity is + * (re)calculated by {@link #updateTrustDb()}. + * + * @param publicKey + * the key whose validity is to be returned. Must not be null. + * @return the validity with bit flags. + * @see #getValidityRaw(PGPPublicKey, PgpUserIdNameHash) + * @deprecated This method exists for compatibility with GnuPG and for easier comparisons between GnuPG's + * calculations and the calculations of this code. Do not use it in your code! Use + * {@link #getValidity(PGPPublicKey)} instead. + */ + @Deprecated + public synchronized int getValidityRaw(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + return _getValidity(publicKey, (PgpUserIdNameHash) null, true); + } + + /** + * Gets the validity of the given user-identity. + *

    + *
  • {@link TrustConst#TRUST_FLAG_DISABLED} - corresponds to {@link #isDisabled(PGPPublicKey)}. + *
  • {@link TrustConst#TRUST_FLAG_REVOKED} - corresponds to {@link PGPPublicKey#hasRevocation()}. + *
  • {@link TrustConst#TRUST_FLAG_PENDING_CHECK} - corresponds to {@link #isTrustDbStale()}. + *
+ *

+ * This method does not calculate the validity! It does solely look it up in the trust-database. The validity is + * (re)calculated by {@link #updateTrustDb()}. + * + * @param publicKey + * the key whose validity is to be returned. Must not be null. + * @param pgpUserIdNameHash + * user-id's (or user-attribute's) name-hash. Must not be null. + * @return the validity with bit flags. + * @see #getValidityRaw(PGPPublicKey) + * @deprecated This method exists for compatibility with GnuPG and for easier comparisons between GnuPG's + * calculations and the calculations of this code. Do not use it in your code! Use + * {@link #getValidity(PGPPublicKey, PgpUserIdNameHash)} instead. + */ + @Deprecated + public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) + { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + return _getValidity(publicKey, pgpUserIdNameHash, true); + } + + /** + * Gets the validity of the given key. + *

+ * The validity of a key is the highest validity of all its user-identities (and -attributes). + *

+ * This method does not calculate the validity! It does solely look it up in the trust-database. The validity is + * (re)calculated by {@link #updateTrustDb()}. + * + * @param pgpKey + * the key whose validity to look up. Must not be null. + * @return the validity of the given {@code publicKey}. Never null. + * @see #getValidity(PgpKey, PgpUserIdNameHash) + * @see #getValidity(PGPPublicKey) + */ + public Validity getValidity(final PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + return getValidity(pgpKey.getPublicKey()); + } + + /** + * Gets the validity of the given user-identity (or -attribute). + *

+ * This method does not calculate the validity! It does solely look it up in the trust-database. The validity is + * (re)calculated by {@link #updateTrustDb()}. + * + * @param pgpUserId + * the user-identity (or -attribute) whose validity to look up. Must not be null. + * @return the validity of the given user-identity. Never null. + * @see #getValidity(PgpKey) + * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) + */ + public Validity getValidity(final PgpUserId pgpUserId) + { + assertNotNull("pgpUserId", pgpUserId); + return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); + } + + /** + * Gets the validity of the given key. + *

+ * The validity of a key is the highest validity of all its user-identities (and -attributes). + *

+ * This method does not calculate the validity! It does solely look it up in the trust-database. The validity is + * (re)calculated by {@link #updateTrustDb()}. + * + * @param publicKey + * the key whose validity to look up. Must not be null. + * @return the validity of the given {@code publicKey}. Never null. + * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) + */ + public Validity getValidity(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); + return Validity.fromNumericValue(numericValue); + } + + /** + * Gets the validity of the given user-identity (or -attribute). + *

+ * This method does not calculate the validity! It does solely look it up in the trust-database. The validity is + * (re)calculated by {@link #updateTrustDb()}. + * + * @param publicKey + * the key whose validity to look up. Must not be null. + * @param pgpUserIdNameHash + * the name-hash of the user-identity (or -attribute) whose validity to look up. Must not be + * null. + * @return the validity of the given user-identity. Never null. + * @see #getValidity(PGPPublicKey) + */ + public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) + { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); + return Validity.fromNumericValue(numericValue); + } + + /** + * Ported from + * {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} + */ + protected synchronized int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, + final boolean withFlags) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return TRUST_UNKNOWN; + + // Loop over all user IDs + long recordNum = trust.getValidList(); + int validity = 0; + int flags = 0; + // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust + // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, + // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). + while (recordNum != 0) + { + TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + assertNotNull("valid", valid); + + if (pgpUserIdNameHash != null) + { + // If a user ID is given we return the validity for that + // user ID ONLY. If the namehash is not found, then there + // is no validity at all (i.e. the user ID wasn't signed). + if (pgpUserIdNameHash.equals(valid.getNameHash())) + { + validity = valid.getValidity() & TRUST_MASK; + flags = valid.getValidity() & ~TRUST_MASK; + break; + } + } + else + { + // If no user ID is given, we take the maximum validity over all user IDs + validity = Math.max(validity, valid.getValidity() & TRUST_MASK); + flags |= valid.getValidity() & ~TRUST_MASK; + } + recordNum = valid.getNext(); + } + + if (withFlags) + { + validity |= flags; + + if ((trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0) + validity |= TRUST_FLAG_DISABLED; + + if (publicKey.hasRevocation()) + validity |= TRUST_FLAG_REVOKED; + + if (isTrustDbStale()) + validity |= TRUST_FLAG_PENDING_CHECK; + } + return validity; + } + + // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) + protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) + { + assertNotNull("pgpUserId", pgpUserId); + assertNonNegativeShort("depth", depth); + assertNonNegativeShort("validity", validity); + assertNonNegativeShort("fullCount", fullCount); + assertNonNegativeShort("marginalCount", marginalCount); + + TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); + if (trust == null) + { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); + trustDbIo.putTrustRecord(trust); + } + + TrustRecord.Valid valid = null; + + // locate an existing Valid record + final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); + long recordNum = trust.getValidList(); + while (recordNum != 0) + { + valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) + break; + + recordNum = valid.getNext(); + } + + if (recordNum == 0) + { // insert a new validity record + valid = new TrustRecord.Valid(); + valid.setNameHash(pgpUserIdNameHashBytes); + valid.setNext(trust.getValidList()); + trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record + trust.setValidList(valid.getRecordNum()); + } + + valid.setValidity((short) validity); + valid.setFullCount((short) fullCount); + valid.setMarginalCount((short) marginalCount); + trust.setDepth((short) depth); + trustDbIo.putTrustRecord(trust); + trustDbIo.putTrustRecord(valid); + } + + private static void assertNonNegativeShort(final String name, final int value) + { + assertNotNull("name", name); + + if (value < 0) + throw new IllegalArgumentException(name + " < 0"); + + if (value > Short.MAX_VALUE) + throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); + } + + /** + * Marks all those keys that we have a secret key for as ultimately trusted. If we have a secret/private key, we + * assume it to be *our* key and we always trust ourselves. + * + * @param onlyIfMissing + * whether only those keys' owner-trust should be set which do not yet have an owner-trust assigned. + */ + public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) + { + for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) + { + if (masterKey.getSecretKey() == null) + continue; + + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); + if (trust == null + || trust.getOwnerTrust() == TRUST_UNKNOWN + || !onlyIfMissing) + { + + if (trust == null) + { + trust = new TrustRecord.Trust(); + trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); + } + + trust.setDepth((short) 0); + trust.setOwnerTrust((short) TRUST_ULTIMATE); + trustDbIo.putTrustRecord(trust); + } + } + } + + protected Set getUltimatelyTrustedKeyFingerprints() + { + Set result = new HashSet(); + TrustRecord record; + long recordNum = 0; + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) + { + if (record.getType() == TrustRecordType.TRUST) + { + TrustRecord.Trust trust = (TrustRecord.Trust) record; + if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) + result.add(new PgpKeyFingerprint(trust.getFingerprint())); + } + } + return result; + } + + public boolean isExpired(PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + + final Date creationTime = publicKey.getCreationTime(); + + final long validSeconds = publicKey.getValidSeconds(); + if (validSeconds != 0) + { + long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); + return validUntilTimestamp < System.currentTimeMillis(); + } + return false; + // TODO there seem to be keys (very old keys) that seem to encode the validity differently. + // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it + // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. + // It's a very small number of keys only, hence I ignore it for now ;-) + } + + /** + * Determines whether the specified key is marked as disabled. + * + * @param pgpKey + * the key whose status to query. Must not be null. + * @return true, if the key is marked as disabled; false, if the key is enabled. + */ + public boolean isDisabled(PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return isDisabled(pgpKey.getPublicKey()); + } + + /** + * Enables or disabled the specified key. + * + * @param pgpKey + * the key whose status to query. Must not be null. + * @param disabled + * true to disable the key; false to enable it. + */ + public void setDisabled(PgpKey pgpKey, final boolean disabled) + { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setDisabled(pgpKey.getPublicKey(), disabled); + } + + /** + * Determines whether the specified key is marked as disabled. + *

+ * The key should be a master-key. + * + * @param publicKey + * the key whose status to query. Must not be null. This should be a master-key. + * @return true, if the key is marked as disabled; false, if the key is enabled. + */ + public boolean isDisabled(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + return false; + + return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; + } + + /** + * Enables or disabled the specified key. + *

+ * The key should be a master-key. + * + * @param publicKey + * the key whose status to query. Must not be null. This should be a master-key. + * @param disabled + * true to disable the key; false to enable it. + */ + public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + { + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrust = trust.getOwnerTrust(); + if (disabled) + ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; + else + ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; + + trust.setOwnerTrust((short) ownerTrust); + + trustDbIo.putTrustRecord(trust); + trustDbIo.flush(); + } + + /** + * Determines if the trust-database is stale. It becomes stale, if it is either explicitly + * {@linkplain #markTrustDbStale() marked stale} or if a key expires. + *

+ * Important: It does not become stale when a key ring file is modified! Thus, when adding new keys, + * {@link #markTrustDbStale()} or {@link #updateTrustDb()} must be invoked. + * + * @return true, if the trust-database is stale; false, if it is up-to-date. + * @see #markTrustDbStale() + * @see #updateTrustDb() + * @see #updateTrustDbIfNeeded() + */ + public synchronized boolean isTrustDbStale() + { + final Config config = Config.getInstance(); + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + if (config.getTrustModel() != version.getTrustModel()) + { + TrustModel configTrustModel; + try + { + configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); + } catch (IllegalArgumentException x) + { + configTrustModel = null; + } + + TrustModel versionTrustModel; + try + { + versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); + } catch (IllegalArgumentException x) + { + versionTrustModel = null; + } + + logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", + config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); + + return true; + } + + if (config.getCompletesNeeded() != version.getCompletesNeeded()) + { + logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", + config.getCompletesNeeded(), version.getCompletesNeeded()); + + return true; + } + + if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) + { + logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", + config.getMarginalsNeeded(), version.getMarginalsNeeded()); + + return true; + } + + if (config.getMaxCertDepth() != version.getCertDepth()) + { + logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", + config.getMaxCertDepth(), version.getCertDepth()); + + return true; + } + + final Date now = new Date(); + if (version.getNextCheck().before(now)) + { + logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", + getDateFormatIso8601WithTime().format(version.getNextCheck()), + getDateFormatIso8601WithTime().format(now)); + + return true; + } + + logger.trace("isTrustDbStale: stale=false"); + return false; + } + + /** + * Marks the trust-db as being stale. + *

+ * Either this method or {@link #updateTrustDb()} must be invoked whenever a new key was added to the key ring, + * because the WOT-related code does not keep track of key-ring-changes ({@link #isTrustDbStale()} does not detect + * them). + * + * @see #isTrustDbStale() + * @see #updateTrustDb() + */ + public synchronized void markTrustDbStale() + { + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + version.setNextCheck(new Date(0)); + trustDbIo.putTrustRecord(version); + } + + /** + * Update the {@code trustdb.gpg} by recalculating all keys' validities, if it is needed. An update is needed, if + * the {@linkplain #isTrustDbStale() trust-db is stale}. + * + * @see #updateTrustDb() + * @see #isTrustDbStale() + */ + public synchronized void updateTrustDbIfNeeded() + { + if (isTrustDbStale()) + updateTrustDb(); + } + + /** + * Update the {@code trustdb.gpg} by recalculating all keys' validities. + *

+ * Either this method or {@link #markTrustDbStale()} must be invoked whenever a new key was added to the key ring, + * because the WOT-related code does not keep track of key-ring-changes ({@link #isTrustDbStale()} does not detect + * them). + *

+ * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, because the + * implementation looked overly complicated. This method here is a re-implementation from scratch. It still seems to + * come very closely to the behaviour of GnuPG's original code. + * + * @see #updateTrustDbIfNeeded() + */ + public synchronized void updateTrustDb() + { + final Config config = Config.getInstance(); + try + { + fingerprint2PgpKeyTrust = new HashMap<>(); + fullTrust = new HashSet<>(); + + startTime = System.currentTimeMillis() / 1000; + nextExpire = Long.MAX_VALUE; + + resetTrustRecords(); + + final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); + if (ultimatelyTrustedKeyFingerprints.isEmpty()) + { + logger.warn("updateTrustDb: There are no ultimately trusted keys!"); + return; + } + + // mark all UTKs as used and fully_trusted and set validity to ultimate + for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) + { + final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); + if (utk == null) + { + logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); + continue; + } + + fullTrust.add(utkFpr); + + for (PgpUserId pgpUserId : utk.getPgpUserIds()) + updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); + + final long expireDate = getExpireTimestamp(utk.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + klist = ultimatelyTrustedKeyFingerprints; + + for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) + { + final List validatedKeys = validateKeyList(); + + klist = new HashSet<>(); + for (PgpKey pgpKey : validatedKeys) + { + PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + klist.add(pgpKey.getPgpKeyFingerprint()); + + for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) + { + final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); + + final int validity = pgpUserIdTrust.getValidity(); + updateValidity(pgpUserId, depth, validity, + pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); + + if (validity >= TRUST_FULLY) + fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); + } + + final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + logger.debug("updateTrustDb: depth={} keys={}", + depth, validatedKeys.size()); + } + + final Date nextExpireDate = new Date(nextExpire * 1000); + trustDbIo.updateVersionRecord(nextExpireDate); + + trustDbIo.flush(); + + logger.info("updateTrustDb: Next trust-db expiration date: {}", + getDateFormatIso8601WithTime().format(nextExpireDate)); + } finally + { + fingerprint2PgpKeyTrust = null; + klist = null; + fullTrust = null; + } + } + + private long getExpireTimestamp(PGPPublicKey pk) + { + final long validSeconds = pk.getValidSeconds(); + if (validSeconds == 0) + return Long.MAX_VALUE; + + final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; + return result; + } + + /** + * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, + * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see + * {@link #updateTrustDb()}. + * + * @return the keys that were processed by this method. + */ + private List validateKeyList() + { + final List result = new ArrayList<>(); + final Set signedPgpKeyFingerprints = new HashSet<>(); + for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) + signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); + + signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted + + for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) + { + final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + { + logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); + continue; + } + result.add(pgpKey); + validateKey(pgpKey); + } + return result; + } + + /** + * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, + * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. + * + * @param pgpKey + * the pgp-key to be validated. Must not be null. + */ + private void validateKey(final PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + logger.debug("validateKey: {}", pgpKey); + + final Config config = Config.getInstance(); + final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + + final boolean expired = isExpired(pgpKey.getPublicKey()); + // final boolean disabled = isDisabled(pgpKey.getPublicKey()); + final boolean revoked = pgpKey.getPublicKey().hasRevocation(); + + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) + { + final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); + + pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 + pgpUserIdTrust.setUltimateCount(0); + pgpUserIdTrust.setFullCount(0); + pgpUserIdTrust.setMarginalCount(0); + + if (expired) + continue; + + // if (disabled) + // continue; + + if (revoked) + continue; + + for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) + { + // It seems, the PGP trust model does not care about the certification level :-( + // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - + // there is no difference (at least according to my tests). + if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION + && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION + && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) + continue; + + final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); + if (signingKey == null) + continue; + + final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); + if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) + && signingOwnerTrust != OwnerTrust.ULTIMATE) + { + // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. + continue; + } + + int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; + if (signingValidity <= TRUST_MARGINAL) + { + // If the signingKey is trusted only marginally or less, we ignore the certification completely. + // Only fully trusted keys are taken into account for transitive trust. + continue; + } + + // The owner-trust of the signing key is relevant. + switch (signingOwnerTrust) + { + case ULTIMATE: + pgpUserIdTrust.incUltimateCount(); + break; + case FULLY: + pgpUserIdTrust.incFullCount(); + break; + case MARGINAL: + pgpUserIdTrust.incMarginalCount(); + break; + default: // ignoring! + break; + } + } + + if (pgpUserIdTrust.getUltimateCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_MARGINAL); + } + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java index bf2b95e93b..49b58290a6 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java @@ -5,21 +5,26 @@ * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class TrustDbException extends RuntimeException { - private static final long serialVersionUID = 1L; +public class TrustDbException extends RuntimeException +{ + private static final long serialVersionUID = 1L; - public TrustDbException() { - } + public TrustDbException() + { + } - public TrustDbException(String message) { - super(message); - } + public TrustDbException(String message) + { + super(message); + } - public TrustDbException(Throwable cause) { - super(cause); - } + public TrustDbException(Throwable cause) + { + super(cause); + } - public TrustDbException(String message, Throwable cause) { - super(message, cause); - } + public TrustDbException(String message, Throwable cause) + { + super(message, cause); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java index 7b822750fa..a0dc6db6a2 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java @@ -4,23 +4,29 @@ /** * Exception thrown by {@link TrustDbIo} when reading from or writing to the trust database failed. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class TrustDbIoException extends TrustDbException { - private static final long serialVersionUID = 1L; +public class TrustDbIoException extends TrustDbException +{ + private static final long serialVersionUID = 1L; - public TrustDbIoException() { - } + public TrustDbIoException() + { + } - public TrustDbIoException(String message) { - super(message); - } + public TrustDbIoException(String message) + { + super(message); + } - public TrustDbIoException(Throwable cause) { - super(cause); - } + public TrustDbIoException(Throwable cause) + { + super(cause); + } - public TrustDbIoException(String message, Throwable cause) { - super(message, cause); - } + public TrustDbIoException(String message, Throwable cause) + { + super(message, cause); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java index 6076178539..c04a6a1417 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java @@ -5,66 +5,72 @@ /** * Trust-model specifying the policy and algorithm of trust/validity calculations. *

- * OpenPGP/GnuPG supports multiple trust models. This implementation, however, currently supports - * {@link #PGP} only. + * OpenPGP/GnuPG supports multiple trust models. This implementation, however, currently supports {@link #PGP} only. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public enum TrustModel { - - CLASSIC(0, "classic"), - PGP(1, "PGP"), - EXTERNAL(2, "external"), - ALWAYS(3, "always"), - DIRECT(4, "direct") - ; - - private final int numericId; - private final String stringId; - - private static TrustModel[] numericId2TrustModel; - - private TrustModel(int numericId, String stringId) { - this.numericId = numericId; - this.stringId = assertNotNull("stringId", stringId); - } - - public int getNumericId() { - return numericId; - } - - public String getStringId() { - return stringId; - } - - @Override - public String toString() { - return stringId; - } - - public static TrustModel fromNumericId(final int numericId) throws IllegalArgumentException { - if (numericId < 0 || numericId >= getNumericId2TrustModel().length) - throw new IllegalArgumentException("numericId unknown: " + numericId); - - final TrustModel trustModel = getNumericId2TrustModel()[numericId]; - if (trustModel == null) - throw new IllegalArgumentException("numericId unknown: " + numericId); - - return trustModel; - } - - private static TrustModel[] getNumericId2TrustModel() { - if (numericId2TrustModel == null) { - int maxNumericId = 0; - for (final TrustModel trustModel : values()) - maxNumericId = Math.max(maxNumericId, trustModel.getNumericId()); - - final TrustModel[] array = new TrustModel[maxNumericId + 1]; - for (final TrustModel trustModel : values()) - array[trustModel.getNumericId()] = trustModel; - - numericId2TrustModel = array; - } - return numericId2TrustModel; - } +public enum TrustModel +{ + CLASSIC(0, "classic"), + PGP(1, "PGP"), + EXTERNAL(2, "external"), + ALWAYS(3, "always"), + DIRECT(4, "direct"); + + private final int numericId; + private final String stringId; + + private static TrustModel[] numericId2TrustModel; + + private TrustModel(int numericId, String stringId) + { + this.numericId = numericId; + this.stringId = assertNotNull("stringId", stringId); + } + + public int getNumericId() + { + return numericId; + } + + public String getStringId() + { + return stringId; + } + + @Override + public String toString() + { + return stringId; + } + + public static TrustModel fromNumericId(final int numericId) throws IllegalArgumentException + { + if (numericId < 0 || numericId >= getNumericId2TrustModel().length) + throw new IllegalArgumentException("numericId unknown: " + numericId); + + final TrustModel trustModel = getNumericId2TrustModel()[numericId]; + if (trustModel == null) + throw new IllegalArgumentException("numericId unknown: " + numericId); + + return trustModel; + } + + private static TrustModel[] getNumericId2TrustModel() + { + if (numericId2TrustModel == null) + { + int maxNumericId = 0; + for (final TrustModel trustModel : values()) + maxNumericId = Math.max(maxNumericId, trustModel.getNumericId()); + + final TrustModel[] array = new TrustModel[maxNumericId + 1]; + for (final TrustModel trustModel : values()) + array[trustModel.getNumericId()] = trustModel; + + numericId2TrustModel = array; + } + return numericId2TrustModel; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java index 7a6e199879..8a4fc53d9c 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java @@ -6,90 +6,91 @@ /** * Validity of a key or user-identity/-attribute. *

- * The validity is calculated by {@link TrustDb#updateTrustDb()} and can be queried by - * its {@link TrustDb#getValidity(org.bouncycastle.openpgp.wot.key.PgpKey) getValidity(PgpKey)} - * or another overloaded {@code getValidity(...)} method. + * The validity is calculated by {@link TrustDb#updateTrustDb()} and can be queried by its + * {@link TrustDb#getValidity(org.bouncycastle.openpgp.wot.key.PgpKey) getValidity(PgpKey)} or another overloaded + * {@code getValidity(...)} method. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public enum Validity { - - /** - * The key/user-identity/user-attribute is not valid or the validity is not known. - *

- * A user should be warned when using or encountering such a key - * (e.g. a red indication colour is a good idea). - */ - NONE(TrustConst.TRUST_UNKNOWN), // 0 - -// EXPIRED(TrustConst.TRUST_EXPIRED), // 1 - - /** - * The key/user-identity/user-attribute is not valid. The 'undefined' state results from - * trust originating transitively from keys being expired, revoked or otherwise not trustworthy, - * anymore. - *

- * A user should be warned when using or encountering such a key - * (e.g. a red indication colour is a good idea). - */ - UNDEFINED(TrustConst.TRUST_UNDEFINED), // 2 - - /** - * The key/user-identity/user-attribute is probably valid. There is some indication for - * its authenticity. - *

- * A user might get a mild warning when using or encountering such a key - * (e.g. a yellow/orange indication colour), though it is - * probably fine. - */ - MARGINAL(TrustConst.TRUST_MARGINAL), // 4 - - /** - * The key/user-identity/user-attribute is valid. There is strong indication for - * its authenticity. - *

- * A user should see some positive confirmation (e.g. a green indication colour) when - * using or encountering such a key. - */ - FULLY(TrustConst.TRUST_FULLY), // 5 - - /** - * The key/user-identity/user-attribute is definitely valid - probably it's belonging to - * the user himself. There is no doubt about its authenticity. - *

- * A user should see some positive confirmation (e.g. a green indication colour) when - * using or encountering such a key. - */ - ULTIMATE(TrustConst.TRUST_ULTIMATE) // 6 - ; - - private final int numericValue; - - private static Map numericValue2Validity; - - private Validity(final int numericValue) { - this.numericValue = numericValue; - } - - public int getNumericValue() { - return numericValue; - } - - public static Validity fromNumericValue(final int numericValue) { - final Validity validity = getNumericValue2Validity().get(numericValue); - if (validity == null) - throw new IllegalArgumentException("numericValue unknown: " + numericValue); - - return validity; - } - - private static Map getNumericValue2Validity() { - if (numericValue2Validity == null) { - Map m = new HashMap<>(values().length); - for (Validity ownerTrust : values()) - m.put(ownerTrust.getNumericValue(), ownerTrust); - - numericValue2Validity = m; - } - return numericValue2Validity; - } +public enum Validity +{ + + /** + * The key/user-identity/user-attribute is not valid or the validity is not known. + *

+ * A user should be warned when using or encountering such a key (e.g. a red indication colour is a good idea). + */ + NONE(TrustConst.TRUST_UNKNOWN), // 0 + +// EXPIRED(TrustConst.TRUST_EXPIRED), // 1 + + /** + * The key/user-identity/user-attribute is not valid. The 'undefined' state results from trust originating + * transitively from keys being expired, revoked or otherwise not trustworthy, anymore. + *

+ * A user should be warned when using or encountering such a key (e.g. a red indication colour is a good idea). + */ + UNDEFINED(TrustConst.TRUST_UNDEFINED), // 2 + + /** + * The key/user-identity/user-attribute is probably valid. There is some indication for its authenticity. + *

+ * A user might get a mild warning when using or encountering such a key (e.g. a yellow/orange indication colour), + * though it is probably fine. + */ + MARGINAL(TrustConst.TRUST_MARGINAL), // 4 + + /** + * The key/user-identity/user-attribute is valid. There is strong indication for its authenticity. + *

+ * A user should see some positive confirmation (e.g. a green indication colour) when using or encountering such a + * key. + */ + FULLY(TrustConst.TRUST_FULLY), // 5 + + /** + * The key/user-identity/user-attribute is definitely valid - probably it's belonging to the user himself. There is + * no doubt about its authenticity. + *

+ * A user should see some positive confirmation (e.g. a green indication colour) when using or encountering such a + * key. + */ + ULTIMATE(TrustConst.TRUST_ULTIMATE) // 6 + ; + + private final int numericValue; + + private static Map numericValue2Validity; + + private Validity(final int numericValue) + { + this.numericValue = numericValue; + } + + public int getNumericValue() + { + return numericValue; + } + + public static Validity fromNumericValue(final int numericValue) + { + final Validity validity = getNumericValue2Validity().get(numericValue); + if (validity == null) + throw new IllegalArgumentException("numericValue unknown: " + numericValue); + + return validity; + } + + private static Map getNumericValue2Validity() + { + if (numericValue2Validity == null) + { + Map m = new HashMap<>(values().length); + for (Validity ownerTrust : values()) + m.put(ownerTrust.getNumericValue(), ownerTrust); + + numericValue2Validity = m; + } + return numericValue2Validity; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java index 5bfd1c81e8..c0eb534af7 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java @@ -12,34 +12,40 @@ import org.bouncycastle.openpgp.wot.key.PgpUserId; import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; -public class PgpKeyTrust { - - private final PgpKey pgpKey; - private final Map nameHash2UserIdTrust = new HashMap<>(); - - public PgpKeyTrust(final PgpKey pgpKey) { - this.pgpKey = assertNotNull("pgpKey", pgpKey); - } - - public PgpKey getPgpKey() { - return pgpKey; - } - - public PgpKeyFingerprint getPgpKeyFingerprint() { - return pgpKey.getPgpKeyFingerprint(); - } - - public PgpUserIdTrust getPgpUserIdTrust(final PgpUserId pgpUserId) { - assertNotNull("pgpUserId", pgpUserId); - PgpUserIdTrust pgpUserIdTrust = nameHash2UserIdTrust.get(pgpUserId.getNameHash()); - if (pgpUserIdTrust == null) { - pgpUserIdTrust = new PgpUserIdTrust(this, pgpUserId); - nameHash2UserIdTrust.put(pgpUserId.getNameHash(), pgpUserIdTrust); - } - return pgpUserIdTrust; - } - - public Collection getPgpUserIdTrusts() { - return Collections.unmodifiableCollection(nameHash2UserIdTrust.values()); - } +public class PgpKeyTrust +{ + private final PgpKey pgpKey; + private final Map nameHash2UserIdTrust = new HashMap<>(); + + public PgpKeyTrust(final PgpKey pgpKey) + { + this.pgpKey = assertNotNull("pgpKey", pgpKey); + } + + public PgpKey getPgpKey() + { + return pgpKey; + } + + public PgpKeyFingerprint getPgpKeyFingerprint() + { + return pgpKey.getPgpKeyFingerprint(); + } + + public PgpUserIdTrust getPgpUserIdTrust(final PgpUserId pgpUserId) + { + assertNotNull("pgpUserId", pgpUserId); + PgpUserIdTrust pgpUserIdTrust = nameHash2UserIdTrust.get(pgpUserId.getNameHash()); + if (pgpUserIdTrust == null) + { + pgpUserIdTrust = new PgpUserIdTrust(this, pgpUserId); + nameHash2UserIdTrust.put(pgpUserId.getNameHash(), pgpUserIdTrust); + } + return pgpUserIdTrust; + } + + public Collection getPgpUserIdTrusts() + { + return Collections.unmodifiableCollection(nameHash2UserIdTrust.values()); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java index 457542ddcd..a63edd0dd6 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java @@ -4,61 +4,81 @@ import org.bouncycastle.openpgp.wot.key.PgpUserId; -public class PgpUserIdTrust { - - private final PgpKeyTrust pgpKeyTrust; - private final PgpUserId pgpUserId; - - private int validity, ultimateCount, fullCount, marginalCount; - - public PgpUserIdTrust(final PgpKeyTrust pgpKeyTrust, final PgpUserId pgpUserId) { - this.pgpKeyTrust = assertNotNull("pgpKeyTrust", pgpKeyTrust); - this.pgpUserId = assertNotNull("pgpUserId", pgpUserId); - } - - public PgpKeyTrust getPgpKeyTrust() { - return pgpKeyTrust; - } - - public PgpUserId getPgpUserId() { - return pgpUserId; - } - - public int getValidity() { - return validity; - } - - public void setValidity(int validity) { - this.validity = validity; - } - - public int getUltimateCount() { - return ultimateCount; - } - public void setUltimateCount(int ultimateCount) { - this.ultimateCount = ultimateCount; - } - public void incUltimateCount() { - ++ultimateCount; - } - - public int getFullCount() { - return fullCount; - } - public void setFullCount(int fullCount) { - this.fullCount = fullCount; - } - public void incFullCount() { - ++fullCount; - } - - public int getMarginalCount() { - return marginalCount; - } - public void setMarginalCount(int marginalCount) { - this.marginalCount = marginalCount; - } - public void incMarginalCount() { - ++marginalCount; - } +public class PgpUserIdTrust +{ + private final PgpKeyTrust pgpKeyTrust; + private final PgpUserId pgpUserId; + + private int validity, ultimateCount, fullCount, marginalCount; + + public PgpUserIdTrust(final PgpKeyTrust pgpKeyTrust, final PgpUserId pgpUserId) + { + this.pgpKeyTrust = assertNotNull("pgpKeyTrust", pgpKeyTrust); + this.pgpUserId = assertNotNull("pgpUserId", pgpUserId); + } + + public PgpKeyTrust getPgpKeyTrust() + { + return pgpKeyTrust; + } + + public PgpUserId getPgpUserId() + { + return pgpUserId; + } + + public int getValidity() + { + return validity; + } + + public void setValidity(int validity) + { + this.validity = validity; + } + + public int getUltimateCount() + { + return ultimateCount; + } + + public void setUltimateCount(int ultimateCount) + { + this.ultimateCount = ultimateCount; + } + + public void incUltimateCount() + { + ++ultimateCount; + } + + public int getFullCount() + { + return fullCount; + } + + public void setFullCount(int fullCount) + { + this.fullCount = fullCount; + } + + public void incFullCount() + { + ++fullCount; + } + + public int getMarginalCount() + { + return marginalCount; + } + + public void setMarginalCount(int marginalCount) + { + this.marginalCount = marginalCount; + } + + public void incMarginalCount() + { + ++marginalCount; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java index e520fd385b..76fed22529 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java @@ -27,656 +27,768 @@ /** * IO layer for GnuPG's {@code trustdb.gpg}. *

- * An instance of this class is used to read from and write to the {@code trustdb.gpg}, which is - * usually located in {@code ~/.gnupg/}. If this file does not exist (yet), it is created. - * The containing directory, however, is not created implicitly! + * An instance of this class is used to read from and write to the {@code trustdb.gpg}, which is usually located in + * {@code ~/.gnupg/}. If this file does not exist (yet), it is created. The containing directory, however, is not + * created implicitly! *

- * Important: Do not use this class directly, if you don't have good reasons to! - * Instead, you should use the {@link TrustDb}. + * Important: Do not use this class directly, if you don't have good reasons to! Instead, you should use + * the {@link TrustDb}. *

* This class was mostly ported from the GnuPG's {@code tdbio.h} and {@code tdbio.c} files. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class TrustDbIo implements AutoCloseable, TrustConst { - private static final Logger logger = LoggerFactory.getLogger(TrustDbIo.class); - - private final SortedMap dirtyRecordNum2TrustRecord = new TreeMap<>(); - private final LinkedHashSet cacheRecordNums = new LinkedHashSet(); - private final Map cacheRecordNum2TrustRecord = new HashMap<>(); - - private final File file; - private final RandomAccessFile raf; - private final FileLock fileLock; - private boolean closed; - - /** - * Create an instance of {@code TrustDbIo} with the given file (usually named {@code trustdb.gpg}). - *

- * Important: You must {@linkplain #close() close} this instance! - * @param file the file to read from and write to. Must not be null. Is created, if - * not yet existing. - * @throws TrustDbIoException if reading from/writing to the {@code trustdb.gpg} failed. - */ - public TrustDbIo(final File file) throws TrustDbIoException { - this.file = assertNotNull("file", file); - try { - this.raf = new RandomAccessFile(file, "rw"); // or better use rwd/rws? maybe manually calling sync is sufficient?! - fileLock = raf.getChannel().lock(); - } catch (IOException x) { - throw new TrustDbIoException(x); - } - - if (getTrustRecord(0, TrustRecord.Version.class) == null) - createVersionRecord(); - } - - private void createVersionRecord() throws TrustDbIoException { - final Config config = Config.getInstance(); - - TrustRecord.Version version = new TrustRecord.Version(); - version.setVersion((short) 3); - version.setCreated(new Date()); - version.setNextCheck(version.getCreated()); // we should check it as soon as possible - version.setMarginalsNeeded(config.getMarginalsNeeded()); - version.setCompletesNeeded(config.getCompletesNeeded()); - version.setCertDepth(config.getMaxCertDepth()); - version.setTrustModel(config.getTrustModel()); // TODO maybe support other trust-models, too - currently only PGP is supported! - version.setMinCertLevel(config.getMinCertLevel()); - - version.setRecordNum(0); - putTrustRecord(version); - flush(); - } - - public synchronized void updateVersionRecord(final Date nextCheck) throws TrustDbIoException { - assertNotNull("nextCheck", nextCheck); - - TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - Config config = Config.getInstance(); - - version.setCreated(new Date()); - version.setNextCheck(nextCheck); - version.setMarginalsNeeded(config.getMarginalsNeeded()); - version.setCompletesNeeded(config.getCompletesNeeded()); - version.setCertDepth(config.getMaxCertDepth()); - version.setTrustModel(config.getTrustModel()); - version.setMinCertLevel(config.getMinCertLevel()); - - putTrustRecord(version); - } - - public TrustRecord getTrustRecord(final long recordNum) throws TrustDbIoException { - return getTrustRecord(recordNum, TrustRecord.class); - } - - public TrustRecord.Trust getTrustByPublicKey(PGPPublicKey pk) throws TrustDbIoException - { - final byte[] fingerprint = pk.getFingerprint(); - return getTrustByFingerprint(fingerprint); - } - - /** Record number of the trust hashtable. */ - private long trustHashRec = 0; - - protected synchronized long getTrustHashRec() { - if (trustHashRec == 0) { - TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - trustHashRec = version.getTrustHashTbl(); - if (trustHashRec == 0) { - createHashTable(0); - trustHashRec = version.getTrustHashTbl(); - } - } - return trustHashRec; - } - - /** - * Append a new empty hashtable to the trustdb. TYPE gives the type - * of the hash table. The only defined type is 0 for a trust hash. - * On return the hashtable has been created, written, the version - * record updated, and the data flushed to the disk. On a fatal error - * the function terminates the process. - */ - private void createHashTable(int type) throws TrustDbIoException { - TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - flush(); // make sure, raf.length is correct. - - long offset; - long recnum; - - try { - offset = raf.length(); - raf.seek(offset); - } catch (IOException e) { - throw new TrustDbIoException(e); - } - - recnum = offset / TRUST_RECORD_LEN; - if (recnum <= 0) // This is will never be the first record. - throw new IllegalStateException("recnum <= 0"); - - if (type == 0) - version.setTrustHashTbl(recnum); - - // Now write the records making up the hash table. - final int n = (256 + ITEMS_PER_HTBL_RECORD - 1) / ITEMS_PER_HTBL_RECORD; - for (int i = 0; i < n; ++i, ++recnum) { - TrustRecord.HashTbl hashTable = new TrustRecord.HashTbl(); - hashTable.setRecordNum(recnum); - putTrustRecord(hashTable); - } - // Update the version record and flush. - putTrustRecord(version); - flush(); - } - - // ulong tdbio_new_recnum () - protected synchronized long newRecordNum() throws TrustDbIoException { - long recordNum; - - // Look for Free records. - final TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - if (version.getFirstFree() != 0) { - recordNum = version.getFirstFree(); - TrustRecord.Free free = getTrustRecord(recordNum, TrustRecord.Free.class); - assertNotNull("free", free); - - // Update dir record. - version.setFirstFree(free.getNext()); - putTrustRecord(version); - - // Zero out the new record. Means we convert from Free to Unused. - // => Done at the end! - } - else { // Not found - append a new record. - final long fileLength; - try { - fileLength = raf.length(); - } catch (IOException e) { - throw new TrustDbIoException(e); - } - recordNum = fileLength / TRUST_RECORD_LEN; - - if (recordNum < 1) // this is will never be the first record - throw new IllegalStateException("recnum < 1"); - - // Maybe our file-length is not up-to-date => consult the dirty records. - if (! dirtyRecordNum2TrustRecord.isEmpty()) { - long lastDirtyRecordNum = dirtyRecordNum2TrustRecord.lastKey(); - if (lastDirtyRecordNum >= recordNum) - recordNum = lastDirtyRecordNum + 1; - } - - // We must add a record, so that the next call to this function returns another recnum. - // => Done at the end! - } - - final TrustRecord.Unused unused = new TrustRecord.Unused(); - unused.setRecordNum(recordNum); - putTrustRecord(unused); - - return recordNum; - } - - public synchronized TrustRecord.Trust getTrustByFingerprint(final byte[] fingerprint) throws TrustDbIoException { - /* Locate the trust record using the hash table */ - TrustRecord rec = getTrustRecordViaHashTable(getTrustHashRec(), fingerprint, new TrustRecordMatcher() { - @Override - public boolean matches(final TrustRecord trustRecord) { - if (! (trustRecord instanceof TrustRecord.Trust)) - return false; - - final TrustRecord.Trust trust = (TrustRecord.Trust) trustRecord; - return Arrays.equals(trust.getFingerprint(), fingerprint); - } - }); - return (TrustRecord.Trust) rec; - } - - private static interface TrustRecordMatcher { - boolean matches(TrustRecord trustRecord); - } - - // static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) - public synchronized TrustRecord getTrustRecordViaHashTable(long table, byte[] key, TrustRecordMatcher matcher) { - long hashrec, item; - int msb; - int level = 0; - - hashrec = table; - next_level: while (true) { - msb = key[level] & 0xff; - hashrec += msb / ITEMS_PER_HTBL_RECORD; - TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); - //assertNotNull("hashTable", hashTable); - if (hashTable == null) - return null; // not found! - - item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); - if (item == 0) - return null; // not found! - - TrustRecord record = getTrustRecord(item); - assertNotNull("record", record); - - if (record.getType() == TrustRecordType.HTBL) { - hashrec = item; - if (++level >= key.length) - throw new TrustDbIoException("hashtable has invalid indirections"); - - continue next_level; - } - - if (record.getType() == TrustRecordType.HLST) { - TrustRecord.HashLst hashList = (TrustRecord.HashLst) record; - - for (;;) { - for (int i = 0; i < ITEMS_PER_HLST_RECORD; i++) { - if (hashList.getRNum(i) != 0) { - TrustRecord tmp = getTrustRecord(hashList.getRNum(i)); - if (tmp != null && matcher.matches(tmp)) - return tmp; - } - } - - if (hashList.getNext() != 0) { - hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); - assertNotNull("hashList", hashList); - } - else - return null; - } - } - - if (matcher.matches(record)) - return record; - else - return null; - } - } - - public synchronized T getTrustRecord(final long recordNum, Class expectedTrustRecordClass) throws TrustDbIoException { - assertNotNull("expectedTrustRecordClass", expectedTrustRecordClass); - final TrustRecordType expectedType = expectedTrustRecordClass == - TrustRecord.class ? null : TrustRecordType.fromClass(expectedTrustRecordClass); - - TrustRecord record = getFromCache(recordNum); - if (record == null) { - try { - raf.seek(recordNum * TRUST_RECORD_LEN); - } catch (IOException x) { - throw new TrustDbIoException(x); - } - - final byte[] buf = new byte[TRUST_RECORD_LEN]; - try { - raf.readFully(buf); - } catch (EOFException x) { - return null; - } catch (IOException x) { - throw new TrustDbIoException(x); - } - - int bufIdx = 0; - - final TrustRecordType type = TrustRecordType.fromId((short) (buf[bufIdx++] & 0xFF)); - if (expectedType != null && ! expectedType.equals(type)) - throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, type)); - - ++bufIdx; // Skip reserved byte. - - switch (type) { - case UNUSED: // unused (free) record - record = new TrustRecord.Unused(); - break; - case VERSION: // version record - final TrustRecord.Version version = new TrustRecord.Version(); - record = version; - - --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. - if (buf[bufIdx++] != 'g' - || buf[bufIdx++] != 'p' - || buf[bufIdx++] != 'g') - throw new TrustDbIoException(String.format("Not a trustdb file: %s", file.getAbsolutePath())); - - version.version = (short) (buf[bufIdx++] & 0xFF); - version.marginalsNeeded = (short) (buf[bufIdx++] & 0xFF); - version.completesNeeded = (short) (buf[bufIdx++] & 0xFF); - version.certDepth = (short) (buf[bufIdx++] & 0xFF); - version.trustModel = (short) (buf[bufIdx++] & 0xFF); - version.minCertLevel = (short) (buf[bufIdx++] & 0xFF); - - bufIdx += 2; // no idea why, but we have to skip 2 bytes - version.created = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); bufIdx += 4; - version.nextCheck = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); bufIdx += 4; - bufIdx += 4; // no idea why, but we have to skip 4 bytes - bufIdx += 4; // no idea why, but we have to skip 4 bytes - version.firstFree = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - bufIdx += 4; // no idea why, but we have to skip 4 bytes - version.trustHashTbl = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - - if (version.version != 3) - throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", version.version, file.getAbsolutePath())); - break; - case FREE: - final TrustRecord.Free free = new TrustRecord.Free(); - record = free; - free.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - break; - case HTBL: - final TrustRecord.HashTbl hashTbl = new TrustRecord.HashTbl(); - record = hashTbl; - for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) { - hashTbl.item[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - } - break; - case HLST: - final TrustRecord.HashLst hashLst = new TrustRecord.HashLst(); - record = hashLst; - hashLst.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { - hashLst.rnum[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - } - break; - case TRUST: - final TrustRecord.Trust trust = new TrustRecord.Trust(); - record = trust; - System.arraycopy(buf, bufIdx, trust.fingerprint, 0, 20); bufIdx += 20; - trust.ownerTrust = (short) (buf[bufIdx++] & 0xFF); - trust.depth = (short) (buf[bufIdx++] & 0xFF); - trust.minOwnerTrust = (short) (buf[bufIdx++] & 0xFF); - ++bufIdx; // no idea why, but we have to skip 1 byte - trust.validList = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - break; - case VALID: - final TrustRecord.Valid valid = new TrustRecord.Valid(); - record = valid; - System.arraycopy(buf, bufIdx, valid.nameHash, 0, 20); bufIdx += 20; - valid.validity = (short) (buf[bufIdx++] & 0xFF); - valid.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; bufIdx += 4; - valid.fullCount = (short) (buf[bufIdx++] & 0xFF); - valid.marginalCount = (short) (buf[bufIdx++] & 0xFF); - break; - default: - throw new IllegalArgumentException("Unexpected TrustRecordType: " + type); - } - record.recordNum = recordNum; - putToCache(record); - } - else { - if (expectedType != null && ! expectedType.equals(record.getType())) - throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, record.getType())); - } - - return expectedTrustRecordClass.cast(record); - } - - public synchronized void putTrustRecord(final TrustRecord trustRecord) throws TrustDbIoException { - assertNotNull("trustRecord", trustRecord); - - if (trustRecord.getRecordNum() < 0) - trustRecord.setRecordNum(newRecordNum()); - - putToCache(trustRecord); - - final long recordNum = trustRecord.getRecordNum(); - dirtyRecordNum2TrustRecord.put(recordNum, trustRecord); - - if (trustRecord instanceof TrustRecord.Trust) - updateHashTable(getTrustHashRec(), ((TrustRecord.Trust) trustRecord).getFingerprint(), recordNum); - } - - protected synchronized void writeTrustRecord(final TrustRecord record) throws TrustDbIoException { - int bufIdx = 0; - final byte[] buf = new byte[TRUST_RECORD_LEN]; - - buf[bufIdx++] = (byte) record.getType().getId(); - ++bufIdx; // Skip reserved byte. - - switch (record.getType()) { - case UNUSED: // unused (free) record - break; - case VERSION: // version record - final TrustRecord.Version version = (TrustRecord.Version) record; - - --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. - buf[bufIdx++] = 'g'; - buf[bufIdx++] = 'p'; - buf[bufIdx++] = 'g'; - - buf[bufIdx++] = (byte) version.version; - buf[bufIdx++] = (byte) version.marginalsNeeded; - buf[bufIdx++] = (byte) version.completesNeeded; - buf[bufIdx++] = (byte) version.certDepth; - buf[bufIdx++] = (byte) version.trustModel; - buf[bufIdx++] = (byte) version.minCertLevel; - - bufIdx += 2; // no idea why, but we have to skip 2 bytes - - intToBytes((int) (version.created.getTime() / 1000L), buf, bufIdx); bufIdx += 4; - intToBytes((int) (version.nextCheck.getTime() / 1000L), buf, bufIdx); bufIdx += 4; - bufIdx += 4; // no idea why, but we have to skip 4 bytes - bufIdx += 4; // no idea why, but we have to skip 4 bytes - intToBytes((int) version.firstFree, buf, bufIdx); bufIdx += 4; - bufIdx += 4; // no idea why, but we have to skip 4 bytes - intToBytes((int) version.trustHashTbl, buf, bufIdx); bufIdx += 4; - - if (version.version != 3) - throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", version.version, file.getAbsolutePath())); - break; - case FREE: - final TrustRecord.Free free = (TrustRecord.Free) record; - intToBytes((int) free.next, buf, bufIdx); bufIdx += 4; - break; - case HTBL: - final TrustRecord.HashTbl hashTbl = (TrustRecord.HashTbl) record; - for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) { - intToBytes((int) hashTbl.item[i], buf, bufIdx); bufIdx += 4; - } - break; - case HLST: - final TrustRecord.HashLst hashLst = (TrustRecord.HashLst) record; - intToBytes((int) hashLst.next, buf, bufIdx); bufIdx += 4; - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { - intToBytes((int) hashLst.rnum[i], buf, bufIdx); bufIdx += 4; - } - break; - case TRUST: - final TrustRecord.Trust trust = (TrustRecord.Trust) record; - System.arraycopy(trust.fingerprint, 0, buf, bufIdx, 20); bufIdx += 20; - buf[bufIdx++] = (byte) trust.ownerTrust; - buf[bufIdx++] = (byte) trust.depth; - buf[bufIdx++] = (byte) trust.minOwnerTrust; - ++bufIdx; // no idea why, but we have to skip 1 byte - intToBytes((int) trust.validList, buf, bufIdx); bufIdx += 4; - break; - case VALID: - final TrustRecord.Valid valid = (TrustRecord.Valid) record; - System.arraycopy(valid.nameHash, 0, buf, bufIdx, 20); bufIdx += 20; - buf[bufIdx++] = (byte) valid.validity; - intToBytes((int) valid.next, buf, bufIdx); bufIdx += 4; - buf[bufIdx++] = (byte) valid.fullCount; - buf[bufIdx++] = (byte) valid.marginalCount; - break; - default: - throw new IllegalArgumentException("Unexpected TrustRecordType: " + record.getType()); - } - - try { - raf.seek(record.getRecordNum() * TRUST_RECORD_LEN); - raf.write(buf); - } catch (IOException e) { - throw new TrustDbIoException(e); - } - } - - /** - * Update a hashtable in the trustdb. TABLE gives the start of the - * table, KEY and KEYLEN are the key, NEWRECNUM is the record number - * to insert into the table. - * - * Return: 0 on success or an error code. - */ - // static int upd_hashtable (ulong table, byte *key, int keylen, ulong newrecnum) - protected synchronized void updateHashTable(long table, byte[] key, long recordNum) throws TrustDbIoException { -// TrustRecord lastrec, rec; - TrustRecord.HashTbl lastHashTable = null; - long hashrec, item; - int msb; - int level = 0; - - hashrec = table; - next_level: while (true) { - msb = key[level] & 0xff; - hashrec += msb / ITEMS_PER_HTBL_RECORD; - - TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); - item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); - if (item == 0) { // Insert a new item into the hash table. - hashTable.setItem(msb % ITEMS_PER_HTBL_RECORD, recordNum); - putTrustRecord(hashTable); - return; - } - else if (item == recordNum) { // perfect match ;-) - return; - } - else { // Must do an update. - lastHashTable = hashTable; hashTable = null; - TrustRecord rec = getTrustRecord(item); - if (rec.getType() == TrustRecordType.HTBL) { - hashrec = item; - ++level; - if (level >= key.length) - throw new TrustDbIoException("hashtable has invalid indirections."); - - continue next_level; - } - else if (rec.getType() == TrustRecordType.HLST) { // Extend the list. - TrustRecord.HashLst hashList = (HashLst) rec; - // Check whether the key is already in this list. - for (;;) { - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { - if (hashList.getRNum(i) == recordNum) - return; // Okay, already in the list. - } - - if (hashList.getNext() == 0) - break; // key is not in the list - - hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); - assertNotNull("hashList", hashList); - } - - // The following line was added by me, Marco. I think the original GnuPG code missed this: We should start looking - // for a free entry in the *first* suitable HashList record again, because there might have been sth. dropped. - hashList = (HashLst) rec; - - // Find the next free entry and put it in. - for (;;) { - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { - if (hashList.getRNum(i) == 0) { - // Empty slot found. - hashList.setRnum(i, recordNum); - putTrustRecord(hashList); - return; // Done. - } - } - - if (hashList.getNext() != 0) { - // read the next reord of the list. - hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); - } - else { - // Append a new record to the list. - TrustRecord.HashLst old = hashList; - hashList = new TrustRecord.HashLst(); - hashList.setRnum(0, recordNum); - - putTrustRecord(hashList); // assigns the new recordNum, too - old.setNext(hashList.getRecordNum()); - putTrustRecord(old); - return; // Done. - } - } /* end loop over list slots */ - } - else { // Insert a list record. - if (rec.getType() != TrustRecordType.TRUST) - throw new IllegalStateException(String.format("hashtbl %d: %d/%d points to an invalid record %d", - table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item)); - - if (rec.getRecordNum() == recordNum) - return; // found - fine - no need to change anything ;-) - - TrustRecord.HashLst hashList = new TrustRecord.HashLst(); - hashList.setRnum(0, rec.getRecordNum()); // Old key record - hashList.setRnum(1, recordNum); // and new key record - putTrustRecord(hashList); - - // Update the hashtable record. - assertNotNull("lastHashTable", lastHashTable).setItem(msb % ITEMS_PER_HTBL_RECORD, hashList.getRecordNum()); - putTrustRecord(lastHashTable); - return; - } - } - } - } - - private TrustRecord getFromCache(final long recordNum) { - final TrustRecord trustRecord = cacheRecordNum2TrustRecord.get(recordNum); - logger.trace("getFromCache: recordNum={} found={}", recordNum, trustRecord != null); - return trustRecord; - } - - private void putToCache(TrustRecord trustRecord) { - assertNotNull("trustRecord", trustRecord); - final long recordNum = trustRecord.getRecordNum(); - - if (cacheRecordNum2TrustRecord.containsKey(recordNum)) - cacheRecordNums.remove(recordNum); - - while (cacheRecordNums.size() + 1 > MAX_CACHE_SIZE) { - final Long oldestRecordNum = cacheRecordNums.iterator().next(); - cacheRecordNums.remove(oldestRecordNum); - cacheRecordNum2TrustRecord.remove(oldestRecordNum); - } - - cacheRecordNum2TrustRecord.put(recordNum, trustRecord); - cacheRecordNums.add(recordNum); - } - - public synchronized void flush() throws TrustDbIoException { - for (TrustRecord trustRecord : dirtyRecordNum2TrustRecord.values()) - writeTrustRecord(trustRecord); - - dirtyRecordNum2TrustRecord.clear(); - - try { - raf.getFD().sync(); - } catch (IOException e) { - throw new TrustDbIoException(e); - } - } - - @Override - public synchronized void close() throws TrustDbIoException { - if (closed) - return; - - flush(); - closed = true; - try { - fileLock.release(); - raf.close(); - } catch (IOException e) { - throw new TrustDbIoException(e); - } - } +public class TrustDbIo implements AutoCloseable, TrustConst +{ + private static final Logger logger = LoggerFactory.getLogger(TrustDbIo.class); + + private final SortedMap dirtyRecordNum2TrustRecord = new TreeMap<>(); + private final LinkedHashSet cacheRecordNums = new LinkedHashSet(); + private final Map cacheRecordNum2TrustRecord = new HashMap<>(); + + private final File file; + private final RandomAccessFile raf; + private final FileLock fileLock; + private boolean closed; + + /** + * Create an instance of {@code TrustDbIo} with the given file (usually named {@code trustdb.gpg}). + *

+ * Important: You must {@linkplain #close() close} this instance! + * + * @param file + * the file to read from and write to. Must not be null. Is created, if not yet existing. + * @throws TrustDbIoException + * if reading from/writing to the {@code trustdb.gpg} failed. + */ + public TrustDbIo(final File file) throws TrustDbIoException + { + this.file = assertNotNull("file", file); + try + { + this.raf = new RandomAccessFile(file, "rw"); // or better use rwd/rws? maybe manually calling sync is + // sufficient?! + fileLock = raf.getChannel().lock(); + } catch (IOException x) + { + throw new TrustDbIoException(x); + } + + if (getTrustRecord(0, TrustRecord.Version.class) == null) + createVersionRecord(); + } + + private void createVersionRecord() throws TrustDbIoException + { + final Config config = Config.getInstance(); + + TrustRecord.Version version = new TrustRecord.Version(); + version.setVersion((short) 3); + version.setCreated(new Date()); + version.setNextCheck(version.getCreated()); // we should check it as soon as possible + version.setMarginalsNeeded(config.getMarginalsNeeded()); + version.setCompletesNeeded(config.getCompletesNeeded()); + version.setCertDepth(config.getMaxCertDepth()); + version.setTrustModel(config.getTrustModel()); // TODO maybe support other trust-models, too - currently only + // PGP is supported! + version.setMinCertLevel(config.getMinCertLevel()); + + version.setRecordNum(0); + putTrustRecord(version); + flush(); + } + + public synchronized void updateVersionRecord(final Date nextCheck) throws TrustDbIoException + { + assertNotNull("nextCheck", nextCheck); + + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + Config config = Config.getInstance(); + + version.setCreated(new Date()); + version.setNextCheck(nextCheck); + version.setMarginalsNeeded(config.getMarginalsNeeded()); + version.setCompletesNeeded(config.getCompletesNeeded()); + version.setCertDepth(config.getMaxCertDepth()); + version.setTrustModel(config.getTrustModel()); + version.setMinCertLevel(config.getMinCertLevel()); + + putTrustRecord(version); + } + + public TrustRecord getTrustRecord(final long recordNum) throws TrustDbIoException + { + return getTrustRecord(recordNum, TrustRecord.class); + } + + public TrustRecord.Trust getTrustByPublicKey(PGPPublicKey pk) throws TrustDbIoException + { + final byte[] fingerprint = pk.getFingerprint(); + return getTrustByFingerprint(fingerprint); + } + + /** Record number of the trust hashtable. */ + private long trustHashRec = 0; + + protected synchronized long getTrustHashRec() + { + if (trustHashRec == 0) + { + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + trustHashRec = version.getTrustHashTbl(); + if (trustHashRec == 0) + { + createHashTable(0); + trustHashRec = version.getTrustHashTbl(); + } + } + return trustHashRec; + } + + /** + * Append a new empty hashtable to the trustdb. TYPE gives the type of the hash table. The only defined type is 0 + * for a trust hash. On return the hashtable has been created, written, the version record updated, and the data + * flushed to the disk. On a fatal error the function terminates the process. + */ + private void createHashTable(int type) throws TrustDbIoException + { + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + flush(); // make sure, raf.length is correct. + + long offset; + long recnum; + + try + { + offset = raf.length(); + raf.seek(offset); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } + + recnum = offset / TRUST_RECORD_LEN; + if (recnum <= 0) // This is will never be the first record. + throw new IllegalStateException("recnum <= 0"); + + if (type == 0) + version.setTrustHashTbl(recnum); + + // Now write the records making up the hash table. + final int n = (256 + ITEMS_PER_HTBL_RECORD - 1) / ITEMS_PER_HTBL_RECORD; + for (int i = 0; i < n; ++i, ++recnum) + { + TrustRecord.HashTbl hashTable = new TrustRecord.HashTbl(); + hashTable.setRecordNum(recnum); + putTrustRecord(hashTable); + } + // Update the version record and flush. + putTrustRecord(version); + flush(); + } + + // ulong tdbio_new_recnum () + protected synchronized long newRecordNum() throws TrustDbIoException + { + long recordNum; + + // Look for Free records. + final TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + if (version.getFirstFree() != 0) + { + recordNum = version.getFirstFree(); + TrustRecord.Free free = getTrustRecord(recordNum, TrustRecord.Free.class); + assertNotNull("free", free); + + // Update dir record. + version.setFirstFree(free.getNext()); + putTrustRecord(version); + + // Zero out the new record. Means we convert from Free to Unused. + // => Done at the end! + } + else + { // Not found - append a new record. + final long fileLength; + try + { + fileLength = raf.length(); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } + recordNum = fileLength / TRUST_RECORD_LEN; + + if (recordNum < 1) // this is will never be the first record + throw new IllegalStateException("recnum < 1"); + + // Maybe our file-length is not up-to-date => consult the dirty records. + if (!dirtyRecordNum2TrustRecord.isEmpty()) + { + long lastDirtyRecordNum = dirtyRecordNum2TrustRecord.lastKey(); + if (lastDirtyRecordNum >= recordNum) + recordNum = lastDirtyRecordNum + 1; + } + + // We must add a record, so that the next call to this function returns another recnum. + // => Done at the end! + } + + final TrustRecord.Unused unused = new TrustRecord.Unused(); + unused.setRecordNum(recordNum); + putTrustRecord(unused); + + return recordNum; + } + + public synchronized TrustRecord.Trust getTrustByFingerprint(final byte[] fingerprint) throws TrustDbIoException + { + /* Locate the trust record using the hash table */ + TrustRecord rec = getTrustRecordViaHashTable(getTrustHashRec(), fingerprint, new TrustRecordMatcher() + { + @Override + public boolean matches(final TrustRecord trustRecord) + { + if (!(trustRecord instanceof TrustRecord.Trust)) + return false; + + final TrustRecord.Trust trust = (TrustRecord.Trust) trustRecord; + return Arrays.equals(trust.getFingerprint(), fingerprint); + } + }); + return (TrustRecord.Trust) rec; + } + + private static interface TrustRecordMatcher + { + boolean matches(TrustRecord trustRecord); + } + + // static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, + // const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) + public synchronized TrustRecord getTrustRecordViaHashTable(long table, byte[] key, TrustRecordMatcher matcher) + { + long hashrec, item; + int msb; + int level = 0; + + hashrec = table; + next_level: while (true) + { + msb = key[level] & 0xff; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); + // assertNotNull("hashTable", hashTable); + if (hashTable == null) + return null; // not found! + + item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); + if (item == 0) + return null; // not found! + + TrustRecord record = getTrustRecord(item); + assertNotNull("record", record); + + if (record.getType() == TrustRecordType.HTBL) + { + hashrec = item; + if (++level >= key.length) + throw new TrustDbIoException("hashtable has invalid indirections"); + + continue next_level; + } + + if (record.getType() == TrustRecordType.HLST) + { + TrustRecord.HashLst hashList = (TrustRecord.HashLst) record; + + for (;;) + { + for (int i = 0; i < ITEMS_PER_HLST_RECORD; i++) + { + if (hashList.getRNum(i) != 0) + { + TrustRecord tmp = getTrustRecord(hashList.getRNum(i)); + if (tmp != null && matcher.matches(tmp)) + return tmp; + } + } + + if (hashList.getNext() != 0) + { + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + assertNotNull("hashList", hashList); + } + else + return null; + } + } + + if (matcher.matches(record)) + return record; + else + return null; + } + } + + public synchronized T getTrustRecord(final long recordNum, Class expectedTrustRecordClass) + throws TrustDbIoException + { + assertNotNull("expectedTrustRecordClass", expectedTrustRecordClass); + final TrustRecordType expectedType = expectedTrustRecordClass == + TrustRecord.class ? null : TrustRecordType.fromClass(expectedTrustRecordClass); + + TrustRecord record = getFromCache(recordNum); + if (record == null) + { + try + { + raf.seek(recordNum * TRUST_RECORD_LEN); + } catch (IOException x) + { + throw new TrustDbIoException(x); + } + + final byte[] buf = new byte[TRUST_RECORD_LEN]; + try + { + raf.readFully(buf); + } catch (EOFException x) + { + return null; + } catch (IOException x) + { + throw new TrustDbIoException(x); + } + + int bufIdx = 0; + + final TrustRecordType type = TrustRecordType.fromId((short) (buf[bufIdx++] & 0xFF)); + if (expectedType != null && !expectedType.equals(type)) + throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, + type)); + + ++bufIdx; // Skip reserved byte. + + switch (type) + { + case UNUSED: // unused (free) record + record = new TrustRecord.Unused(); + break; + case VERSION: // version record + final TrustRecord.Version version = new TrustRecord.Version(); + record = version; + + --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. + if (buf[bufIdx++] != 'g' + || buf[bufIdx++] != 'p' + || buf[bufIdx++] != 'g') + throw new TrustDbIoException(String.format("Not a trustdb file: %s", file.getAbsolutePath())); + + version.version = (short) (buf[bufIdx++] & 0xFF); + version.marginalsNeeded = (short) (buf[bufIdx++] & 0xFF); + version.completesNeeded = (short) (buf[bufIdx++] & 0xFF); + version.certDepth = (short) (buf[bufIdx++] & 0xFF); + version.trustModel = (short) (buf[bufIdx++] & 0xFF); + version.minCertLevel = (short) (buf[bufIdx++] & 0xFF); + + bufIdx += 2; // no idea why, but we have to skip 2 bytes + version.created = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); + bufIdx += 4; + version.nextCheck = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); + bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + bufIdx += 4; // no idea why, but we have to skip 4 bytes + version.firstFree = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + version.trustHashTbl = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + + if (version.version != 3) + throw new TrustDbIoException(String.format( + "Wrong version number (3 expected, but %d found): %s", version.version, + file.getAbsolutePath())); + break; + case FREE: + final TrustRecord.Free free = new TrustRecord.Free(); + record = free; + free.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + break; + case HTBL: + final TrustRecord.HashTbl hashTbl = new TrustRecord.HashTbl(); + record = hashTbl; + for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) + { + hashTbl.item[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + } + break; + case HLST: + final TrustRecord.HashLst hashLst = new TrustRecord.HashLst(); + record = hashLst; + hashLst.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + { + hashLst.rnum[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + } + break; + case TRUST: + final TrustRecord.Trust trust = new TrustRecord.Trust(); + record = trust; + System.arraycopy(buf, bufIdx, trust.fingerprint, 0, 20); + bufIdx += 20; + trust.ownerTrust = (short) (buf[bufIdx++] & 0xFF); + trust.depth = (short) (buf[bufIdx++] & 0xFF); + trust.minOwnerTrust = (short) (buf[bufIdx++] & 0xFF); + ++bufIdx; // no idea why, but we have to skip 1 byte + trust.validList = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + break; + case VALID: + final TrustRecord.Valid valid = new TrustRecord.Valid(); + record = valid; + System.arraycopy(buf, bufIdx, valid.nameHash, 0, 20); + bufIdx += 20; + valid.validity = (short) (buf[bufIdx++] & 0xFF); + valid.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + valid.fullCount = (short) (buf[bufIdx++] & 0xFF); + valid.marginalCount = (short) (buf[bufIdx++] & 0xFF); + break; + default: + throw new IllegalArgumentException("Unexpected TrustRecordType: " + type); + } + record.recordNum = recordNum; + putToCache(record); + } + else + { + if (expectedType != null && !expectedType.equals(record.getType())) + throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, + record.getType())); + } + + return expectedTrustRecordClass.cast(record); + } + + public synchronized void putTrustRecord(final TrustRecord trustRecord) throws TrustDbIoException + { + assertNotNull("trustRecord", trustRecord); + + if (trustRecord.getRecordNum() < 0) + trustRecord.setRecordNum(newRecordNum()); + + putToCache(trustRecord); + + final long recordNum = trustRecord.getRecordNum(); + dirtyRecordNum2TrustRecord.put(recordNum, trustRecord); + + if (trustRecord instanceof TrustRecord.Trust) + updateHashTable(getTrustHashRec(), ((TrustRecord.Trust) trustRecord).getFingerprint(), recordNum); + } + + protected synchronized void writeTrustRecord(final TrustRecord record) throws TrustDbIoException + { + int bufIdx = 0; + final byte[] buf = new byte[TRUST_RECORD_LEN]; + + buf[bufIdx++] = (byte) record.getType().getId(); + ++bufIdx; // Skip reserved byte. + + switch (record.getType()) + { + case UNUSED: // unused (free) record + break; + case VERSION: // version record + final TrustRecord.Version version = (TrustRecord.Version) record; + + --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. + buf[bufIdx++] = 'g'; + buf[bufIdx++] = 'p'; + buf[bufIdx++] = 'g'; + + buf[bufIdx++] = (byte) version.version; + buf[bufIdx++] = (byte) version.marginalsNeeded; + buf[bufIdx++] = (byte) version.completesNeeded; + buf[bufIdx++] = (byte) version.certDepth; + buf[bufIdx++] = (byte) version.trustModel; + buf[bufIdx++] = (byte) version.minCertLevel; + + bufIdx += 2; // no idea why, but we have to skip 2 bytes + + intToBytes((int) (version.created.getTime() / 1000L), buf, bufIdx); + bufIdx += 4; + intToBytes((int) (version.nextCheck.getTime() / 1000L), buf, bufIdx); + bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + bufIdx += 4; // no idea why, but we have to skip 4 bytes + intToBytes((int) version.firstFree, buf, bufIdx); + bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + intToBytes((int) version.trustHashTbl, buf, bufIdx); + bufIdx += 4; + + if (version.version != 3) + throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", + version.version, file.getAbsolutePath())); + break; + case FREE: + final TrustRecord.Free free = (TrustRecord.Free) record; + intToBytes((int) free.next, buf, bufIdx); + bufIdx += 4; + break; + case HTBL: + final TrustRecord.HashTbl hashTbl = (TrustRecord.HashTbl) record; + for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) + { + intToBytes((int) hashTbl.item[i], buf, bufIdx); + bufIdx += 4; + } + break; + case HLST: + final TrustRecord.HashLst hashLst = (TrustRecord.HashLst) record; + intToBytes((int) hashLst.next, buf, bufIdx); + bufIdx += 4; + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + { + intToBytes((int) hashLst.rnum[i], buf, bufIdx); + bufIdx += 4; + } + break; + case TRUST: + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + System.arraycopy(trust.fingerprint, 0, buf, bufIdx, 20); + bufIdx += 20; + buf[bufIdx++] = (byte) trust.ownerTrust; + buf[bufIdx++] = (byte) trust.depth; + buf[bufIdx++] = (byte) trust.minOwnerTrust; + ++bufIdx; // no idea why, but we have to skip 1 byte + intToBytes((int) trust.validList, buf, bufIdx); + bufIdx += 4; + break; + case VALID: + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + System.arraycopy(valid.nameHash, 0, buf, bufIdx, 20); + bufIdx += 20; + buf[bufIdx++] = (byte) valid.validity; + intToBytes((int) valid.next, buf, bufIdx); + bufIdx += 4; + buf[bufIdx++] = (byte) valid.fullCount; + buf[bufIdx++] = (byte) valid.marginalCount; + break; + default: + throw new IllegalArgumentException("Unexpected TrustRecordType: " + record.getType()); + } + + try + { + raf.seek(record.getRecordNum() * TRUST_RECORD_LEN); + raf.write(buf); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } + } + + /** + * Update a hashtable in the trustdb. TABLE gives the start of the table, KEY and KEYLEN are the key, NEWRECNUM is + * the record number to insert into the table. + * + * Return: 0 on success or an error code. + */ + // static int upd_hashtable (ulong table, byte *key, int keylen, ulong newrecnum) + protected synchronized void updateHashTable(long table, byte[] key, long recordNum) throws TrustDbIoException + { + // TrustRecord lastrec, rec; + TrustRecord.HashTbl lastHashTable = null; + long hashrec, item; + int msb; + int level = 0; + + hashrec = table; + next_level: while (true) + { + msb = key[level] & 0xff; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + + TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); + item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); + if (item == 0) + { // Insert a new item into the hash table. + hashTable.setItem(msb % ITEMS_PER_HTBL_RECORD, recordNum); + putTrustRecord(hashTable); + return; + } + else if (item == recordNum) + { // perfect match ;-) + return; + } + else + { // Must do an update. + lastHashTable = hashTable; + hashTable = null; + TrustRecord rec = getTrustRecord(item); + if (rec.getType() == TrustRecordType.HTBL) + { + hashrec = item; + ++level; + if (level >= key.length) + throw new TrustDbIoException("hashtable has invalid indirections."); + + continue next_level; + } + else if (rec.getType() == TrustRecordType.HLST) + { // Extend the list. + TrustRecord.HashLst hashList = (HashLst) rec; + // Check whether the key is already in this list. + for (;;) + { + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + { + if (hashList.getRNum(i) == recordNum) + return; // Okay, already in the list. + } + + if (hashList.getNext() == 0) + break; // key is not in the list + + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + assertNotNull("hashList", hashList); + } + + // The following line was added by me, Marco. I think the original GnuPG code missed this: We should + // start looking + // for a free entry in the *first* suitable HashList record again, because there might have been + // sth. dropped. + hashList = (HashLst) rec; + + // Find the next free entry and put it in. + for (;;) + { + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + { + if (hashList.getRNum(i) == 0) + { + // Empty slot found. + hashList.setRnum(i, recordNum); + putTrustRecord(hashList); + return; // Done. + } + } + + if (hashList.getNext() != 0) + { + // read the next reord of the list. + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + } + else + { + // Append a new record to the list. + TrustRecord.HashLst old = hashList; + hashList = new TrustRecord.HashLst(); + hashList.setRnum(0, recordNum); + + putTrustRecord(hashList); // assigns the new recordNum, too + old.setNext(hashList.getRecordNum()); + putTrustRecord(old); + return; // Done. + } + } /* end loop over list slots */ + } + else + { // Insert a list record. + if (rec.getType() != TrustRecordType.TRUST) + throw new IllegalStateException(String.format( + "hashtbl %d: %d/%d points to an invalid record %d", + table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item)); + + if (rec.getRecordNum() == recordNum) + return; // found - fine - no need to change anything ;-) + + TrustRecord.HashLst hashList = new TrustRecord.HashLst(); + hashList.setRnum(0, rec.getRecordNum()); // Old key record + hashList.setRnum(1, recordNum); // and new key record + putTrustRecord(hashList); + + // Update the hashtable record. + assertNotNull("lastHashTable", lastHashTable).setItem(msb % ITEMS_PER_HTBL_RECORD, + hashList.getRecordNum()); + putTrustRecord(lastHashTable); + return; + } + } + } + } + + private TrustRecord getFromCache(final long recordNum) + { + final TrustRecord trustRecord = cacheRecordNum2TrustRecord.get(recordNum); + logger.trace("getFromCache: recordNum={} found={}", recordNum, trustRecord != null); + return trustRecord; + } + + private void putToCache(TrustRecord trustRecord) + { + assertNotNull("trustRecord", trustRecord); + final long recordNum = trustRecord.getRecordNum(); + + if (cacheRecordNum2TrustRecord.containsKey(recordNum)) + cacheRecordNums.remove(recordNum); + + while (cacheRecordNums.size() + 1 > MAX_CACHE_SIZE) + { + final Long oldestRecordNum = cacheRecordNums.iterator().next(); + cacheRecordNums.remove(oldestRecordNum); + cacheRecordNum2TrustRecord.remove(oldestRecordNum); + } + + cacheRecordNum2TrustRecord.put(recordNum, trustRecord); + cacheRecordNums.add(recordNum); + } + + public synchronized void flush() throws TrustDbIoException + { + for (TrustRecord trustRecord : dirtyRecordNum2TrustRecord.values()) + writeTrustRecord(trustRecord); + + dirtyRecordNum2TrustRecord.clear(); + + try + { + raf.getFD().sync(); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } + } + + @Override + public synchronized void close() throws TrustDbIoException + { + if (closed) + return; + + flush(); + closed = true; + try + { + fileLock.release(); + raf.close(); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java index 666de9bef9..d72a68f786 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java @@ -5,476 +5,526 @@ import org.bouncycastle.openpgp.wot.TrustConst; -public abstract class TrustRecord implements TrustConst { - - protected long recordNum = -1; - - public static class Unused extends TrustRecord { - @Override - public TrustRecordType getType() { - return TrustRecordType.UNUSED; - } - } - - public static class Version extends TrustRecord { - protected short version; // should be 3 - protected short marginalsNeeded; - protected short completesNeeded; - protected short certDepth; - protected short trustModel; - protected short minCertLevel; - protected Date created; // timestamp of trustdb creation - protected Date nextCheck; // timestamp of next scheduled check - protected long reserved; - protected long reserved2; - protected long firstFree; - protected long reserved3; - protected long trustHashTbl; - - @Override - public TrustRecordType getType() { - return TrustRecordType.VERSION; - } - - public short getVersion() { - return version; - } - public void setVersion(short version) { - this.version = version; - } - - public short getMarginalsNeeded() { - return marginalsNeeded; - } - public void setMarginalsNeeded(short marginals) { - this.marginalsNeeded = marginals; - } - - public short getCompletesNeeded() { - return completesNeeded; - } - public void setCompletesNeeded(short completes) { - this.completesNeeded = completes; - } - - public short getCertDepth() { - return certDepth; - } - public void setCertDepth(short certDepth) { - this.certDepth = certDepth; - } - - public short getTrustModel() { - return trustModel; - } - public void setTrustModel(short trustModel) { - this.trustModel = trustModel; - } - - public short getMinCertLevel() { - return minCertLevel; - } - public void setMinCertLevel(short minCertLevel) { - this.minCertLevel = minCertLevel; - } - - public Date getCreated() { - return created; - } - public void setCreated(Date created) { - this.created = created; - } - - public Date getNextCheck() { - return nextCheck; - } - public void setNextCheck(Date nextCheck) { - this.nextCheck = nextCheck; - } - - public long getReserved() { - return reserved; - } - - public long getReserved2() { - return reserved2; - } - - public long getFirstFree() { - return firstFree; - } - public void setFirstFree(long firstFree) { - this.firstFree = firstFree; - } - - public long getReserved3() { - return reserved3; - } - - public long getTrustHashTbl() { - return trustHashTbl; - } - public void setTrustHashTbl(long trustHashTbl) { - this.trustHashTbl = trustHashTbl; - } - -// @Override -// public Version clone() { -// Version clone = (Version) super.clone(); -// clone.created = (Date) created.clone(); -// clone.nextCheck = (Date) nextCheck.clone(); -// return clone; -// } - - @Override - public String toString() { - return String.format("%s[recordNum=%d version=%d marginalsNeeded=%d completesNeeded=%d certDepth=%d trustModel=%d minCertLevel=%d created=%s nextCheck=%s reserved=%d reserved2=%d firstFree=%d reserved3=%d trustHashTbl=%d]", - getClass().getSimpleName(), recordNum, version, marginalsNeeded, - completesNeeded, certDepth, trustModel, minCertLevel, - created, nextCheck, reserved, reserved2, firstFree, reserved3, trustHashTbl); - } - } - - public static class Free extends TrustRecord { - protected long next; - - public long getNext() { - return next; - } - public void setNext(long next) { - if (next < 0) - throw new IllegalArgumentException("next < 0"); - - this.next = next; - } - - @Override - public TrustRecordType getType() { - return TrustRecordType.FREE; - } - - @Override - public String toString() { - return String.format("%s[recordNum=%d next=%d]", - getClass().getSimpleName(), recordNum, next); - } - } - - public static class HashTbl extends TrustRecord { - protected long[] item = new long[ITEMS_PER_HTBL_RECORD]; - - public long getItem(int index) { - return item[index]; - } - public void setItem(int index, long value) { - if (value < 0) - throw new IllegalArgumentException("value < 0"); - - item[index] = value; - } - - @Override - public TrustRecordType getType() { - return TrustRecordType.HTBL; - } - -// @Override -// public HashTbl clone() { -// HashTbl clone = (HashTbl) super.clone(); -// clone.item = new long[item.length]; -// System.arraycopy(item, 0, clone.item, 0, item.length); -// return clone; -// } - - @Override - public String toString() { - return String.format("%s[recordNum=%d item=%s]", - getClass().getSimpleName(), recordNum, Arrays.toString(item)); - } - } - - public static class HashLst extends TrustRecord { - protected long next; - protected long[] rnum = new long[ITEMS_PER_HLST_RECORD]; // of another record - - @Override - public TrustRecordType getType() { - return TrustRecordType.HLST; - } - - public long getRNum(int index) { - return rnum[index]; - } - public void setRnum(int index, long value) { - if (value < 0) - throw new IllegalArgumentException("value < 0"); - - rnum[index] = value; - } - - public long getNext() { - return next; - } - public void setNext(long next) { - if (next < 0) - throw new IllegalArgumentException("next < 0"); - - this.next = next; - } - -// @Override -// public HashLst clone() { -// HashLst clone = (HashLst) super.clone(); -// clone.rnum = new long[rnum.length]; -// System.arraycopy(rnum, 0, clone.rnum, 0, rnum.length); -// return clone; -// } - - @Override - public String toString() { - return String.format("%s[recordNum=%d next=%d rnum=%s]", - getClass().getSimpleName(), recordNum, next, Arrays.toString(rnum)); - } - } - - public static class Trust extends TrustRecord { - protected byte[] fingerprint = new byte[20]; - protected short ownerTrust; - protected short depth; - protected long validList; - protected short minOwnerTrust; - - public Trust() { - } - - @Override - public TrustRecordType getType() { - return TrustRecordType.TRUST; - } - - public byte[] getFingerprint() { - return fingerprint; - } - public void setFingerprint(byte[] fingerprint) { - this.fingerprint = fingerprint; - } - - public short getOwnerTrust() { - return ownerTrust; - } - public void setOwnerTrust(short ownerTrust) { - this.ownerTrust = ownerTrust; - } - - public short getDepth() { - return depth; - } - public void setDepth(short depth) { - if (depth < 0) - throw new IllegalArgumentException("depth < 0"); - - this.depth = depth; - } - - public long getValidList() { - return validList; - } - public void setValidList(long validList) { - if (validList < 0) - throw new IllegalArgumentException("validList < 0"); - - this.validList = validList; - } - - public short getMinOwnerTrust() { - return minOwnerTrust; - } - public void setMinOwnerTrust(short minOwnerTrust) { - this.minOwnerTrust = minOwnerTrust; - } - -// @Override -// public Trust clone() { -// Trust clone = (Trust) super.clone(); -// clone.fingerprint = new byte[fingerprint.length]; -// System.arraycopy(fingerprint, 0, clone.fingerprint, 0, fingerprint.length); -// return clone; -// } - - @Override - public String toString() { - return String.format("%s[recordNum=%d fingerprint=%s ownerTrust=%d depth=%d validList=%d minOwnerTrust=%d]", - getClass().getSimpleName(), recordNum, encodeHexStr(fingerprint), - ownerTrust, depth, validList, minOwnerTrust); - } - } - - public static class Valid extends TrustRecord { - protected byte[] nameHash = new byte[20]; - protected long next; - protected short validity; - protected short fullCount; - protected short marginalCount; - - @Override - public TrustRecordType getType() { - return TrustRecordType.VALID; - } - - public byte[] getNameHash() { - return nameHash; - } - public void setNameHash(byte[] nameHash) { - this.nameHash = nameHash; - } - - public long getNext() { - return next; - } - public void setNext(long next) { - if (next < 0) - throw new IllegalArgumentException("next < 0"); - - this.next = next; - } - - public short getValidity() { - return validity; - } - - public void setValidity(short validity) { - this.validity = validity; - } - - public short getFullCount() { - return fullCount; - } - - public void setFullCount(short fullCount) { - this.fullCount = fullCount; - } - - public short getMarginalCount() { - return marginalCount; - } - - public void setMarginalCount(short marginalCount) { - this.marginalCount = marginalCount; - } - -// @Override -// public Valid clone() { -// Valid clone = (Valid) super.clone(); -// clone.nameHash = new byte[nameHash.length]; -// System.arraycopy(nameHash, 0, clone.nameHash, 0, nameHash.length); -// return clone; -// } - - @Override - public String toString() { - return String.format("%s[recordNum=%d nameHash=%s next=%d validity=%d fullCount=%d marginalCount=%d]", - getClass().getSimpleName(), recordNum, encodeHexStr(nameHash), - next, validity, fullCount, marginalCount); - } - }; - - public long getRecordNum() { - return recordNum; - } - - protected void setRecordNum(long recordNum) { - this.recordNum = recordNum; - } - - public abstract TrustRecordType getType(); - -// @Override -// public TrustRecord clone() { -// TrustRecord clone; -// try { -// clone = (TrustRecord) super.clone(); -// } catch (CloneNotSupportedException e) { -// throw new RuntimeException(e); -// } -// return clone; -// } - - // Copied from tdbio.c: - // struct trust_record { - // int rectype; - // int mark; - // int dirty; /* for now only used internal by functions */ - // struct trust_record *next; /* help pointer to build lists in memory */ - // ulong recnum; - // union { - // struct { /* version record: */ - // byte version; /* should be 3 */ - // byte marginalsNeeded; - // byte completesNeeded; - // byte cert_depth; - // byte trust_model; - // byte min_cert_level; - // ulong created; /* timestamp of trustdb creation */ - // ulong nextcheck; /* timestamp of next scheduled check */ - // ulong reserved; - // ulong reserved2; - // ulong firstfree; - // ulong reserved3; - // ulong trusthashtbl; - // } ver; - // struct { /* free record */ - // ulong next; - // } free; - // struct { - // ulong item[ITEMS_PER_HTBL_RECORD]; - // } htbl; - // struct { - // ulong next; - // ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */ - // } hlst; - // struct { - // byte fingerprint[20]; - // byte ownertrust; - // byte depth; - // ulong validlist; - // byte min_ownertrust; - // } trust; - // struct { - // byte namehash[20]; - // ulong next; - // byte validity; - // byte full_count; - // byte marginal_count; - // } valid; - // } r; - // }; - - public static String encodeHexStr(final byte[] buf) - { - return encodeHexStr(buf, 0, buf.length); - } - - /** - * Encode a byte array into a human readable hex string. For each byte, - * two hex digits are produced. They are concatenated without any separators. - * - * @param buf The byte array to translate into human readable text. - * @param pos The start position (0-based). - * @param len The number of bytes that shall be processed beginning at the position specified by pos. - * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. - * @see #encodeHexStr(byte[]) - * @see #decodeHexStr(String) - */ - public static String encodeHexStr(final byte[] buf, int pos, int len) - { - final StringBuilder hex = new StringBuilder(); - while (len-- > 0) { - final byte ch = buf[pos++]; - int d = (ch >> 4) & 0xf; - hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); - d = ch & 0xf; - hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); - } - return hex.toString(); - } +public abstract class TrustRecord implements TrustConst +{ + + protected long recordNum = -1; + + public static class Unused extends TrustRecord + { + @Override + public TrustRecordType getType() + { + return TrustRecordType.UNUSED; + } + } + + public static class Version extends TrustRecord + { + protected short version; // should be 3 + protected short marginalsNeeded; + protected short completesNeeded; + protected short certDepth; + protected short trustModel; + protected short minCertLevel; + protected Date created; // timestamp of trustdb creation + protected Date nextCheck; // timestamp of next scheduled check + protected long reserved; + protected long reserved2; + protected long firstFree; + protected long reserved3; + protected long trustHashTbl; + + @Override + public TrustRecordType getType() + { + return TrustRecordType.VERSION; + } + + public short getVersion() + { + return version; + } + + public void setVersion(short version) + { + this.version = version; + } + + public short getMarginalsNeeded() + { + return marginalsNeeded; + } + + public void setMarginalsNeeded(short marginals) + { + this.marginalsNeeded = marginals; + } + + public short getCompletesNeeded() + { + return completesNeeded; + } + + public void setCompletesNeeded(short completes) + { + this.completesNeeded = completes; + } + + public short getCertDepth() + { + return certDepth; + } + + public void setCertDepth(short certDepth) + { + this.certDepth = certDepth; + } + + public short getTrustModel() + { + return trustModel; + } + + public void setTrustModel(short trustModel) + { + this.trustModel = trustModel; + } + + public short getMinCertLevel() + { + return minCertLevel; + } + + public void setMinCertLevel(short minCertLevel) + { + this.minCertLevel = minCertLevel; + } + + public Date getCreated() + { + return created; + } + + public void setCreated(Date created) + { + this.created = created; + } + + public Date getNextCheck() + { + return nextCheck; + } + + public void setNextCheck(Date nextCheck) + { + this.nextCheck = nextCheck; + } + + public long getReserved() + { + return reserved; + } + + public long getReserved2() + { + return reserved2; + } + + public long getFirstFree() + { + return firstFree; + } + + public void setFirstFree(long firstFree) + { + this.firstFree = firstFree; + } + + public long getReserved3() + { + return reserved3; + } + + public long getTrustHashTbl() + { + return trustHashTbl; + } + + public void setTrustHashTbl(long trustHashTbl) + { + this.trustHashTbl = trustHashTbl; + } + + @Override + public String toString() + { + return String.format("%s[recordNum=%d version=%d marginalsNeeded=%d completesNeeded=%d certDepth=%d trustModel=%d minCertLevel=%d created=%s nextCheck=%s reserved=%d reserved2=%d firstFree=%d reserved3=%d trustHashTbl=%d]", + getClass().getSimpleName(), recordNum, version, marginalsNeeded, + completesNeeded, certDepth, trustModel, minCertLevel, + created, nextCheck, reserved, reserved2, firstFree, reserved3, trustHashTbl); + } + } + + public static class Free extends TrustRecord + { + protected long next; + + public long getNext() + { + return next; + } + + public void setNext(long next) + { + if (next < 0) + throw new IllegalArgumentException("next < 0"); + + this.next = next; + } + + @Override + public TrustRecordType getType() + { + return TrustRecordType.FREE; + } + + @Override + public String toString() + { + return String.format("%s[recordNum=%d next=%d]", + getClass().getSimpleName(), recordNum, next); + } + } + + public static class HashTbl extends TrustRecord + { + protected long[] item = new long[ITEMS_PER_HTBL_RECORD]; + + public long getItem(int index) + { + return item[index]; + } + + public void setItem(int index, long value) + { + if (value < 0) + throw new IllegalArgumentException("value < 0"); + + item[index] = value; + } + + @Override + public TrustRecordType getType() + { + return TrustRecordType.HTBL; + } + + @Override + public String toString() + { + return String.format("%s[recordNum=%d item=%s]", + getClass().getSimpleName(), recordNum, Arrays.toString(item)); + } + } + + public static class HashLst extends TrustRecord + { + protected long next; + protected long[] rnum = new long[ITEMS_PER_HLST_RECORD]; // of another record + + @Override + public TrustRecordType getType() + { + return TrustRecordType.HLST; + } + + public long getRNum(int index) + { + return rnum[index]; + } + + public void setRnum(int index, long value) + { + if (value < 0) + throw new IllegalArgumentException("value < 0"); + + rnum[index] = value; + } + + public long getNext() + { + return next; + } + + public void setNext(long next) + { + if (next < 0) + throw new IllegalArgumentException("next < 0"); + + this.next = next; + } + + @Override + public String toString() + { + return String.format("%s[recordNum=%d next=%d rnum=%s]", + getClass().getSimpleName(), recordNum, next, Arrays.toString(rnum)); + } + } + + public static class Trust extends TrustRecord + { + protected byte[] fingerprint = new byte[20]; + protected short ownerTrust; + protected short depth; + protected long validList; + protected short minOwnerTrust; + + public Trust() + { + } + + @Override + public TrustRecordType getType() + { + return TrustRecordType.TRUST; + } + + public byte[] getFingerprint() + { + return fingerprint; + } + + public void setFingerprint(byte[] fingerprint) + { + this.fingerprint = fingerprint; + } + + public short getOwnerTrust() + { + return ownerTrust; + } + + public void setOwnerTrust(short ownerTrust) + { + this.ownerTrust = ownerTrust; + } + + public short getDepth() + { + return depth; + } + + public void setDepth(short depth) + { + if (depth < 0) + throw new IllegalArgumentException("depth < 0"); + + this.depth = depth; + } + + public long getValidList() + { + return validList; + } + + public void setValidList(long validList) + { + if (validList < 0) + throw new IllegalArgumentException("validList < 0"); + + this.validList = validList; + } + + public short getMinOwnerTrust() + { + return minOwnerTrust; + } + + public void setMinOwnerTrust(short minOwnerTrust) + { + this.minOwnerTrust = minOwnerTrust; + } + + @Override + public String toString() + { + return String.format( + "%s[recordNum=%d fingerprint=%s ownerTrust=%d depth=%d validList=%d minOwnerTrust=%d]", + getClass().getSimpleName(), recordNum, encodeHexStr(fingerprint), + ownerTrust, depth, validList, minOwnerTrust); + } + } + + public static class Valid extends TrustRecord + { + protected byte[] nameHash = new byte[20]; + protected long next; + protected short validity; + protected short fullCount; + protected short marginalCount; + + @Override + public TrustRecordType getType() + { + return TrustRecordType.VALID; + } + + public byte[] getNameHash() + { + return nameHash; + } + + public void setNameHash(byte[] nameHash) + { + this.nameHash = nameHash; + } + + public long getNext() + { + return next; + } + + public void setNext(long next) + { + if (next < 0) + throw new IllegalArgumentException("next < 0"); + + this.next = next; + } + + public short getValidity() + { + return validity; + } + + public void setValidity(short validity) + { + this.validity = validity; + } + + public short getFullCount() + { + return fullCount; + } + + public void setFullCount(short fullCount) + { + this.fullCount = fullCount; + } + + public short getMarginalCount() + { + return marginalCount; + } + + public void setMarginalCount(short marginalCount) + { + this.marginalCount = marginalCount; + } + + @Override + public String toString() + { + return String.format("%s[recordNum=%d nameHash=%s next=%d validity=%d fullCount=%d marginalCount=%d]", + getClass().getSimpleName(), recordNum, encodeHexStr(nameHash), + next, validity, fullCount, marginalCount); + } + }; + + public long getRecordNum() + { + return recordNum; + } + + protected void setRecordNum(long recordNum) + { + this.recordNum = recordNum; + } + + public abstract TrustRecordType getType(); + + // Copied from tdbio.c: + // struct trust_record { + // int rectype; + // int mark; + // int dirty; /* for now only used internal by functions */ + // struct trust_record *next; /* help pointer to build lists in memory */ + // ulong recnum; + // union { + // struct { /* version record: */ + // byte version; /* should be 3 */ + // byte marginalsNeeded; + // byte completesNeeded; + // byte cert_depth; + // byte trust_model; + // byte min_cert_level; + // ulong created; /* timestamp of trustdb creation */ + // ulong nextcheck; /* timestamp of next scheduled check */ + // ulong reserved; + // ulong reserved2; + // ulong firstfree; + // ulong reserved3; + // ulong trusthashtbl; + // } ver; + // struct { /* free record */ + // ulong next; + // } free; + // struct { + // ulong item[ITEMS_PER_HTBL_RECORD]; + // } htbl; + // struct { + // ulong next; + // ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */ + // } hlst; + // struct { + // byte fingerprint[20]; + // byte ownertrust; + // byte depth; + // ulong validlist; + // byte min_ownertrust; + // } trust; + // struct { + // byte namehash[20]; + // ulong next; + // byte validity; + // byte full_count; + // byte marginal_count; + // } valid; + // } r; + // }; + + public static String encodeHexStr(final byte[] buf) + { + return encodeHexStr(buf, 0, buf.length); + } + + /** + * Encode a byte array into a human readable hex string. For each byte, two hex digits are produced. They are + * concatenated without any separators. + * + * @param buf + * The byte array to translate into human readable text. + * @param pos + * The start position (0-based). + * @param len + * The number of bytes that shall be processed beginning at the position specified by pos. + * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. + * @see #encodeHexStr(byte[]) + * @see #decodeHexStr(String) + */ + public static String encodeHexStr(final byte[] buf, int pos, int len) + { + final StringBuilder hex = new StringBuilder(); + while (len-- > 0) + { + final byte ch = buf[pos++]; + int d = (ch >> 4) & 0xf; + hex.append((char) (d >= 10 ? 'a' - 10 + d : '0' + d)); + d = ch & 0xf; + hex.append((char) (d >= 10 ? 'a' - 10 + d : '0' + d)); + } + return hex.toString(); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java index 489a949c47..619c4139e1 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java @@ -6,70 +6,80 @@ import java.util.HashMap; import java.util.Map; -public enum TrustRecordType { - UNUSED((short) 0, TrustRecord.Unused.class), - VERSION((short) 1, TrustRecord.Version.class), - HTBL((short) 10, TrustRecord.HashTbl.class), - HLST((short) 11, TrustRecord.HashLst.class), - TRUST((short) 12, TrustRecord.Trust.class), - VALID((short) 13, TrustRecord.Valid.class), - FREE((short) 254, TrustRecord.Free.class); - - private static Map id2Type; - private static Map, TrustRecordType> class2Type; - - private final short id; - private Class trustRecordClass; - - private TrustRecordType(short id, Class trustRecordClass) { - this.id = id; - this.trustRecordClass = assertNotNull("trustRecordClass", trustRecordClass); - } - - public short getId() { - return id; - } - - public Class getTrustRecordClass() { - return trustRecordClass; - } - - public static TrustRecordType fromId(short id) { - TrustRecordType type = getId2Type().get(id); - if (type == null) - throw new IllegalArgumentException("id unknown: " + id); - - return type; - } - - public static TrustRecordType fromClass(Class trustRecordClass) { - assertNotNull("trustRecordClass", trustRecordClass); - TrustRecordType type = getClass2Type().get(trustRecordClass); - if (type == null) - throw new IllegalArgumentException("trustRecordClass unknown: " + trustRecordClass.getName()); - - return type; - } - - private static Map getId2Type() { - if (id2Type == null) { - Map m = new HashMap<>(values().length); - for (TrustRecordType type : values()) - m.put(type.getId(), type); - - id2Type = Collections.unmodifiableMap(m); - } - return id2Type; - } - - private static Map, TrustRecordType> getClass2Type() { - if (class2Type == null) { - Map, TrustRecordType> m = new HashMap<>(values().length); - for (TrustRecordType type : values()) - m.put(type.getTrustRecordClass(), type); - - class2Type = Collections.unmodifiableMap(m); - } - return class2Type; - } +public enum TrustRecordType +{ + UNUSED((short) 0, TrustRecord.Unused.class), + VERSION((short) 1, TrustRecord.Version.class), + HTBL((short) 10, TrustRecord.HashTbl.class), + HLST((short) 11, TrustRecord.HashLst.class), + TRUST((short) 12, TrustRecord.Trust.class), + VALID((short) 13, TrustRecord.Valid.class), + FREE((short) 254, TrustRecord.Free.class); + + private static Map id2Type; + private static Map, TrustRecordType> class2Type; + + private final short id; + private Class trustRecordClass; + + private TrustRecordType(short id, Class trustRecordClass) + { + this.id = id; + this.trustRecordClass = assertNotNull("trustRecordClass", trustRecordClass); + } + + public short getId() + { + return id; + } + + public Class getTrustRecordClass() + { + return trustRecordClass; + } + + public static TrustRecordType fromId(short id) + { + TrustRecordType type = getId2Type().get(id); + if (type == null) + throw new IllegalArgumentException("id unknown: " + id); + + return type; + } + + public static TrustRecordType fromClass(Class trustRecordClass) + { + assertNotNull("trustRecordClass", trustRecordClass); + TrustRecordType type = getClass2Type().get(trustRecordClass); + if (type == null) + throw new IllegalArgumentException("trustRecordClass unknown: " + trustRecordClass.getName()); + + return type; + } + + private static Map getId2Type() + { + if (id2Type == null) + { + Map m = new HashMap<>(values().length); + for (TrustRecordType type : values()) + m.put(type.getId(), type); + + id2Type = Collections.unmodifiableMap(m); + } + return id2Type; + } + + private static Map, TrustRecordType> getClass2Type() + { + if (class2Type == null) + { + Map, TrustRecordType> m = new HashMap<>(values().length); + for (TrustRecordType type : values()) + m.put(type.getTrustRecordClass(), type); + + class2Type = Collections.unmodifiableMap(m); + } + return class2Type; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java index 2492e53131..737b1cebd5 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java @@ -1,133 +1,152 @@ package org.bouncycastle.openpgp.wot.internal; -public class Util { - - private Util() { - } - - public static byte[] longToBytes(final long value) { - final byte[] bytes = new byte[8]; - longToBytes(value, bytes, 0); - return bytes; - } - public static void longToBytes(final long value, final byte[] bytes, final int index) { - assertNotNull("bytes", bytes); - if (bytes.length - index < 8) - throw new IllegalArgumentException("bytes.length - index < 8"); - - for (int i = 0; i < 8; ++i) - bytes[index + i] = (byte) (value >>> (8 * (8 - 1 - i))); - } - - public static long bytesToLong(final byte[] bytes) { - assertNotNull("bytes", bytes); - if (bytes.length != 8) - throw new IllegalArgumentException("bytes.length != 8"); - - return bytesToLong(bytes, 0); - } - public static long bytesToLong(final byte[] bytes, final int index) { - assertNotNull("bytes", bytes); - if (bytes.length - index < 8) - throw new IllegalArgumentException("bytes.length - index < 8"); - - long value = 0; - for (int i = 0; i < 8; ++i) - value |= ((long) (bytes[index + i] & 0xff)) << (8 * (8 - 1 - i)); - - return value; - } - - public static byte[] intToBytes(final int value) { - final byte[] bytes = new byte[4]; - intToBytes(value, bytes, 0); - return bytes; - } - - public static void intToBytes(final int value, final byte[] bytes, final int index) { - assertNotNull("bytes", bytes); - if (bytes.length - index < 4) - throw new IllegalArgumentException("bytes.length - index < 4"); - - for (int i = 0; i < 4; ++i) - bytes[index + i] = (byte) (value >>> (8 * (4 - 1 - i))); - } - - public static int bytesToInt(final byte[] bytes) { - assertNotNull("bytes", bytes); - if (bytes.length != 4) - throw new IllegalArgumentException("bytes.length != 4"); - - return bytesToInt(bytes, 0); - } - - public static int bytesToInt(final byte[] bytes, final int index) { - assertNotNull("bytes", bytes); - if (bytes.length - index < 4) - throw new IllegalArgumentException("bytes.length - index < 4"); - - int value = 0; - for (int i = 0; i < 4; ++i) - value |= ((long) (bytes[index + i] & 0xff)) << (8 * (4 - 1 - i)); - - return value; - } - - public static String encodeHexStr(final byte[] buf) - { - return encodeHexStr(buf, 0, buf.length); - } - - /** - * Encode a byte array into a human readable hex string. For each byte, - * two hex digits are produced. They are concatenated without any separators. - * - * @param buf The byte array to translate into human readable text. - * @param pos The start position (0-based). - * @param len The number of bytes that shall be processed beginning at the position specified by pos. - * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. - * @see #encodeHexStr(byte[]) - * @see #decodeHexStr(String) - */ - public static String encodeHexStr(final byte[] buf, int pos, int len) - { - final StringBuilder hex = new StringBuilder(); - while (len-- > 0) { - final byte ch = buf[pos++]; - int d = (ch >> 4) & 0xf; - hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); - d = ch & 0xf; - hex.append((char)(d >= 10 ? 'a' - 10 + d : '0' + d)); - } - return hex.toString(); - } - - /** - * Decode a string containing two hex digits for each byte. - * @param hex The hex encoded string - * @return The byte array represented by the given hex string - * @see #encodeHexStr(byte[]) - * @see #encodeHexStr(byte[], int, int) - */ - public static byte[] decodeHexStr(final String hex) - { - if (hex.length() % 2 != 0) - throw new IllegalArgumentException("The hex string must have an even number of characters!"); - - final byte[] res = new byte[hex.length() / 2]; - - int m = 0; - for (int i = 0; i < hex.length(); i += 2) { - res[m++] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); - } - - return res; - } - - public static final T assertNotNull(final String name, final T object) { - if (object == null) - throw new IllegalArgumentException(String.format("%s == null", name)); - - return object; - } +public class Util +{ + private Util() + { + } + + public static byte[] longToBytes(final long value) + { + final byte[] bytes = new byte[8]; + longToBytes(value, bytes, 0); + return bytes; + } + + public static void longToBytes(final long value, final byte[] bytes, final int index) + { + assertNotNull("bytes", bytes); + if (bytes.length - index < 8) + throw new IllegalArgumentException("bytes.length - index < 8"); + + for (int i = 0; i < 8; ++i) + bytes[index + i] = (byte) (value >>> (8 * (8 - 1 - i))); + } + + public static long bytesToLong(final byte[] bytes) + { + assertNotNull("bytes", bytes); + if (bytes.length != 8) + throw new IllegalArgumentException("bytes.length != 8"); + + return bytesToLong(bytes, 0); + } + + public static long bytesToLong(final byte[] bytes, final int index) + { + assertNotNull("bytes", bytes); + if (bytes.length - index < 8) + throw new IllegalArgumentException("bytes.length - index < 8"); + + long value = 0; + for (int i = 0; i < 8; ++i) + value |= ((long) (bytes[index + i] & 0xff)) << (8 * (8 - 1 - i)); + + return value; + } + + public static byte[] intToBytes(final int value) + { + final byte[] bytes = new byte[4]; + intToBytes(value, bytes, 0); + return bytes; + } + + public static void intToBytes(final int value, final byte[] bytes, final int index) + { + assertNotNull("bytes", bytes); + if (bytes.length - index < 4) + throw new IllegalArgumentException("bytes.length - index < 4"); + + for (int i = 0; i < 4; ++i) + bytes[index + i] = (byte) (value >>> (8 * (4 - 1 - i))); + } + + public static int bytesToInt(final byte[] bytes) + { + assertNotNull("bytes", bytes); + if (bytes.length != 4) + throw new IllegalArgumentException("bytes.length != 4"); + + return bytesToInt(bytes, 0); + } + + public static int bytesToInt(final byte[] bytes, final int index) + { + assertNotNull("bytes", bytes); + if (bytes.length - index < 4) + throw new IllegalArgumentException("bytes.length - index < 4"); + + int value = 0; + for (int i = 0; i < 4; ++i) + value |= ((long) (bytes[index + i] & 0xff)) << (8 * (4 - 1 - i)); + + return value; + } + + public static String encodeHexStr(final byte[] buf) + { + return encodeHexStr(buf, 0, buf.length); + } + + /** + * Encode a byte array into a human readable hex string. For each byte, two hex digits are produced. They are + * concatenated without any separators. + * + * @param buf + * The byte array to translate into human readable text. + * @param pos + * The start position (0-based). + * @param len + * The number of bytes that shall be processed beginning at the position specified by pos. + * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. + * @see #encodeHexStr(byte[]) + * @see #decodeHexStr(String) + */ + public static String encodeHexStr(final byte[] buf, int pos, int len) + { + final StringBuilder hex = new StringBuilder(); + while (len-- > 0) + { + final byte ch = buf[pos++]; + int d = (ch >> 4) & 0xf; + hex.append((char) (d >= 10 ? 'a' - 10 + d : '0' + d)); + d = ch & 0xf; + hex.append((char) (d >= 10 ? 'a' - 10 + d : '0' + d)); + } + return hex.toString(); + } + + /** + * Decode a string containing two hex digits for each byte. + * + * @param hex + * The hex encoded string + * @return The byte array represented by the given hex string + * @see #encodeHexStr(byte[]) + * @see #encodeHexStr(byte[], int, int) + */ + public static byte[] decodeHexStr(final String hex) + { + if (hex.length() % 2 != 0) + throw new IllegalArgumentException("The hex string must have an even number of characters!"); + + final byte[] res = new byte[hex.length() / 2]; + + int m = 0; + for (int i = 0; i < hex.length(); i += 2) + { + res[m++] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); + } + + return res; + } + + public static final T assertNotNull(final String name, final T object) + { + if (object == null) + throw new IllegalArgumentException(String.format("%s == null", name)); + + return object; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java index a5a7d8ce6f..93fecb0bd2 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java @@ -17,137 +17,172 @@ /** * OpenPGP key or key pair (if both public and secret key are present). + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpKey { - - private final PgpKeyId pgpKeyId; - - private final PgpKeyFingerprint pgpKeyFingerprint; - - private PGPPublicKeyRing publicKeyRing; - - private PGPSecretKeyRing secretKeyRing; - - private PGPPublicKey publicKey; - - private PGPSecretKey secretKey; - - private PgpKey masterKey; - - // A sub-key may be added twice, because we enlist from both the secret *and* public key ring - // collection. Therefore, we now use a LinkedHashSet (instead of an ArrayList). - private Set subKeyIds; - - private List subKeys; - - private List pgpUserIds; - - public PgpKey(final PgpKeyId pgpKeyId, final PgpKeyFingerprint pgpKeyFingerprint) { - this.pgpKeyId = assertNotNull("pgpKeyId", pgpKeyId); - this.pgpKeyFingerprint = assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); - } - - public PgpKeyId getPgpKeyId() { - return pgpKeyId; - } - - public PgpKeyFingerprint getPgpKeyFingerprint() { - return pgpKeyFingerprint; - } - - public PGPPublicKeyRing getPublicKeyRing() { - return publicKeyRing; - } - protected void setPublicKeyRing(PGPPublicKeyRing publicKeyRing) { - this.publicKeyRing = publicKeyRing; - } - - public PGPSecretKeyRing getSecretKeyRing() { - return secretKeyRing; - } - protected void setSecretKeyRing(PGPSecretKeyRing secretKeyRing) { - this.secretKeyRing = secretKeyRing; - } - - public PGPPublicKey getPublicKey() { - return publicKey; - } - protected void setPublicKey(final PGPPublicKey publicKey) { - this.publicKey = publicKey; - } - - public PGPSecretKey getSecretKey() { - return secretKey; - } - protected void setSecretKey(final PGPSecretKey secretKey) { - this.secretKey = secretKey; - } - - public List getPgpUserIds() { - if (pgpUserIds == null) { - final List l = new ArrayList<>(); - - for (final Iterator it = publicKey.getUserIDs(); it.hasNext(); ) { - final String userId = (String) it.next(); - l.add(new PgpUserId(this, userId)); - } - - for (final Iterator it = publicKey.getUserAttributes(); it.hasNext(); ) { - final PGPUserAttributeSubpacketVector userAttribute = (PGPUserAttributeSubpacketVector) it.next(); - l.add(new PgpUserId(this, userAttribute)); - } - pgpUserIds = Collections.unmodifiableList(l); - } - return pgpUserIds; - } - - /** - * Gets the master-key for this key. - * @return the master-key for this key. Always null, if this is a master-key. - * Never null, if this is a sub-key. - * @see #getSubKeyIds() - * @see #getSubKeys() - */ - public PgpKey getMasterKey() { - return masterKey; - } - protected void setMasterKey(PgpKey masterKey) { - this.masterKey = masterKey; - } - - public Set getSubKeyIds() { - if (subKeyIds == null && masterKey == null) // only a master-key can have sub-keys! hence we keep it null, if this is not a master-key! - subKeyIds = new LinkedHashSet<>(); - - return subKeyIds; - } - protected void setSubKeyIds(Set subKeyIds) { - this.subKeyIds = subKeyIds; - } - - /** - * Gets the sub-keys. - * @return the sub-keys. Never null, if this is a master-key. Always null, if this is a sub-key. - * @see #getMasterKey() - * @see #getSubKeyIds() - */ - public List getSubKeys() { - return subKeys; - } - protected void setSubKeys(List subKeys) { - this.subKeys = subKeys; - } - - @Override - public String toString() { - final Iterator userIdIt = publicKey.getUserIDs(); - final String primaryUserId; - if (userIdIt == null || ! userIdIt.hasNext()) - primaryUserId = null; - else - primaryUserId = (String) userIdIt.next(); - - return String.format("%s[pgpKeyId=%s masterKey=%s primaryUserId=%s]", this.getClass().getSimpleName(), pgpKeyId, masterKey, primaryUserId); - } +public class PgpKey +{ + private final PgpKeyId pgpKeyId; + + private final PgpKeyFingerprint pgpKeyFingerprint; + + private PGPPublicKeyRing publicKeyRing; + + private PGPSecretKeyRing secretKeyRing; + + private PGPPublicKey publicKey; + + private PGPSecretKey secretKey; + + private PgpKey masterKey; + + // A sub-key may be added twice, because we enlist from both the secret *and* public key ring + // collection. Therefore, we now use a LinkedHashSet (instead of an ArrayList). + private Set subKeyIds; + + private List subKeys; + + private List pgpUserIds; + + public PgpKey(final PgpKeyId pgpKeyId, final PgpKeyFingerprint pgpKeyFingerprint) + { + this.pgpKeyId = assertNotNull("pgpKeyId", pgpKeyId); + this.pgpKeyFingerprint = assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); + } + + public PgpKeyId getPgpKeyId() + { + return pgpKeyId; + } + + public PgpKeyFingerprint getPgpKeyFingerprint() + { + return pgpKeyFingerprint; + } + + public PGPPublicKeyRing getPublicKeyRing() + { + return publicKeyRing; + } + + protected void setPublicKeyRing(PGPPublicKeyRing publicKeyRing) + { + this.publicKeyRing = publicKeyRing; + } + + public PGPSecretKeyRing getSecretKeyRing() + { + return secretKeyRing; + } + + protected void setSecretKeyRing(PGPSecretKeyRing secretKeyRing) + { + this.secretKeyRing = secretKeyRing; + } + + public PGPPublicKey getPublicKey() + { + return publicKey; + } + + protected void setPublicKey(final PGPPublicKey publicKey) + { + this.publicKey = publicKey; + } + + public PGPSecretKey getSecretKey() + { + return secretKey; + } + + protected void setSecretKey(final PGPSecretKey secretKey) + { + this.secretKey = secretKey; + } + + public List getPgpUserIds() + { + if (pgpUserIds == null) + { + final List l = new ArrayList<>(); + + for (final Iterator it = publicKey.getUserIDs(); it.hasNext();) + { + final String userId = (String) it.next(); + l.add(new PgpUserId(this, userId)); + } + + for (final Iterator it = publicKey.getUserAttributes(); it.hasNext();) + { + final PGPUserAttributeSubpacketVector userAttribute = (PGPUserAttributeSubpacketVector) it.next(); + l.add(new PgpUserId(this, userAttribute)); + } + pgpUserIds = Collections.unmodifiableList(l); + } + return pgpUserIds; + } + + /** + * Gets the master-key for this key. + * + * @return the master-key for this key. Always null, if this is a master-key. Never null, + * if this is a sub-key. + * @see #getSubKeyIds() + * @see #getSubKeys() + */ + public PgpKey getMasterKey() + { + return masterKey; + } + + protected void setMasterKey(PgpKey masterKey) + { + this.masterKey = masterKey; + } + + public Set getSubKeyIds() + { + if (subKeyIds == null && masterKey == null) // only a master-key can have sub-keys! hence we keep it null, if + // this is not a master-key! + subKeyIds = new LinkedHashSet<>(); + + return subKeyIds; + } + + protected void setSubKeyIds(Set subKeyIds) + { + this.subKeyIds = subKeyIds; + } + + /** + * Gets the sub-keys. + * + * @return the sub-keys. Never null, if this is a master-key. Always null, if this is a + * sub-key. + * @see #getMasterKey() + * @see #getSubKeyIds() + */ + public List getSubKeys() + { + return subKeys; + } + + protected void setSubKeys(List subKeys) + { + this.subKeys = subKeys; + } + + @Override + public String toString() + { + final Iterator userIdIt = publicKey.getUserIDs(); + final String primaryUserId; + if (userIdIt == null || !userIdIt.hasNext()) + primaryUserId = null; + else + primaryUserId = (String) userIdIt.next(); + + return String.format("%s[pgpKeyId=%s masterKey=%s primaryUserId=%s]", this.getClass().getSimpleName(), + pgpKeyId, masterKey, primaryUserId); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java index 0fce858265..f0c6d9025c 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java @@ -9,93 +9,111 @@ /** * An OpenPGP key's fingerprint. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpKeyFingerprint implements Comparable, Serializable { - private static final long serialVersionUID = 1L; - - private final byte[] fingerprint; - private transient int hashCode; - private transient WeakReference toString; - private transient WeakReference toHumanString; - - public PgpKeyFingerprint(final byte[] fingerprint) { - assertNotNull("fingerprint", fingerprint); - // In order to guarantee that this instance is immutable, we must copy the input. - this.fingerprint = copyOf(fingerprint, fingerprint.length); - } - - public PgpKeyFingerprint(final String fingerprint) { - assertNotNull("fingerprint", fingerprint); - this.fingerprint = decodeHexStr(fingerprint); - } - - public byte[] getBytes() { - // In order to guarantee that this instance stays immutable, we copy the byte array. - return copyOf(fingerprint, fingerprint.length); - } - - @Override - public int hashCode() { - if (hashCode == 0) - hashCode = Arrays.hashCode(fingerprint); - - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - - final PgpKeyFingerprint other = (PgpKeyFingerprint) obj; - return Arrays.equals(fingerprint, other.fingerprint); - } - - @Override - public int compareTo(PgpKeyFingerprint o) { - int res = Integer.compare(this.fingerprint.length, o.fingerprint.length); - if (res != 0) - return res; - - for (int i = 0; i < this.fingerprint.length; i++) { - res = Byte.compare(this.fingerprint[i], o.fingerprint[i]); - if (res != 0) - return res; - } - return 0; - } - - @Override - public String toString() { - String s = toString == null ? null : toString.get(); - if (s == null) { - s = encodeHexStr(fingerprint); - toString = new WeakReference(s); - } - return s; - } - - public String toHumanString() { - String s = toHumanString == null ? null : toHumanString.get(); - if (s == null) { - s = _toHumanString(); - toHumanString = new WeakReference(s); - } - return s; - } - - private String _toHumanString() { - final StringBuilder sb = new StringBuilder(); - final String string = toString(); - - for (int i = 0; i < string.length(); ++i) { - if (i > 0 && (i % 4 == 0)) - sb.append(' '); - - sb.append(string.charAt(i)); - } - return sb.toString(); - } +public class PgpKeyFingerprint implements Comparable, Serializable +{ + private static final long serialVersionUID = 1L; + + private final byte[] fingerprint; + private transient int hashCode; + private transient WeakReference toString; + private transient WeakReference toHumanString; + + public PgpKeyFingerprint(final byte[] fingerprint) + { + assertNotNull("fingerprint", fingerprint); + // In order to guarantee that this instance is immutable, we must copy the input. + this.fingerprint = copyOf(fingerprint, fingerprint.length); + } + + public PgpKeyFingerprint(final String fingerprint) + { + assertNotNull("fingerprint", fingerprint); + this.fingerprint = decodeHexStr(fingerprint); + } + + public byte[] getBytes() + { + // In order to guarantee that this instance stays immutable, we copy the byte array. + return copyOf(fingerprint, fingerprint.length); + } + + @Override + public int hashCode() + { + if (hashCode == 0) + hashCode = Arrays.hashCode(fingerprint); + + return hashCode; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + final PgpKeyFingerprint other = (PgpKeyFingerprint) obj; + return Arrays.equals(fingerprint, other.fingerprint); + } + + @Override + public int compareTo(PgpKeyFingerprint o) + { + int res = Integer.compare(this.fingerprint.length, o.fingerprint.length); + if (res != 0) + return res; + + for (int i = 0; i < this.fingerprint.length; i++) + { + res = Byte.compare(this.fingerprint[i], o.fingerprint[i]); + if (res != 0) + return res; + } + return 0; + } + + @Override + public String toString() + { + String s = toString == null ? null : toString.get(); + if (s == null) + { + s = encodeHexStr(fingerprint); + toString = new WeakReference(s); + } + return s; + } + + public String toHumanString() + { + String s = toHumanString == null ? null : toHumanString.get(); + if (s == null) + { + s = _toHumanString(); + toHumanString = new WeakReference(s); + } + return s; + } + + private String _toHumanString() + { + final StringBuilder sb = new StringBuilder(); + final String string = toString(); + + for (int i = 0; i < string.length(); ++i) + { + if (i > 0 && (i % 4 == 0)) + sb.append(' '); + + sb.append(string.charAt(i)); + } + return sb.toString(); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java index 0e384355cb..b64b095166 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java @@ -7,84 +7,98 @@ /** * An OpenPGP key's (unique) identifier. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpKeyId implements Comparable, Serializable { - private static final long serialVersionUID = 1L; +public class PgpKeyId implements Comparable, Serializable +{ + private static final long serialVersionUID = 1L; - private final long pgpKeyId; - private transient WeakReference toString; - private transient WeakReference toHumanString; + private final long pgpKeyId; + private transient WeakReference toString; + private transient WeakReference toHumanString; - public PgpKeyId(final long pgpKeyId) { - this.pgpKeyId = pgpKeyId; - } + public PgpKeyId(final long pgpKeyId) + { + this.pgpKeyId = pgpKeyId; + } - public PgpKeyId(final String pgpKeyIdString) { - this(bytesToLong(decodeHexStr(assertNotNull("pgpKeyIdString", pgpKeyIdString)))); - } + public PgpKeyId(final String pgpKeyIdString) + { + this(bytesToLong(decodeHexStr(assertNotNull("pgpKeyIdString", pgpKeyIdString)))); + } - @Override - public String toString() { - String s = toString == null ? null : toString.get(); - if (s == null) { - s = encodeHexStr(longToBytes(pgpKeyId)); - toString = new WeakReference(s); - } - return s; - } + @Override + public String toString() + { + String s = toString == null ? null : toString.get(); + if (s == null) + { + s = encodeHexStr(longToBytes(pgpKeyId)); + toString = new WeakReference(s); + } + return s; + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (pgpKeyId ^ (pgpKeyId >>> 32)); - return result; - } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (int) (pgpKeyId ^ (pgpKeyId >>> 32)); + return result; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final PgpKeyId other = (PgpKeyId) obj; - return this.pgpKeyId == other.pgpKeyId; - } + @Override + public boolean equals(final Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final PgpKeyId other = (PgpKeyId) obj; + return this.pgpKeyId == other.pgpKeyId; + } - @Override - public int compareTo(PgpKeyId other) { - assertNotNull("other", other); - // Same semantics as for normal numbers. - return (this.pgpKeyId < other.pgpKeyId ? -1 : - (this.pgpKeyId > other.pgpKeyId ? 1 : 0)); - } + @Override + public int compareTo(PgpKeyId other) + { + assertNotNull("other", other); + // Same semantics as for normal numbers. + return (this.pgpKeyId < other.pgpKeyId ? -1 : + (this.pgpKeyId > other.pgpKeyId ? 1 : 0)); + } - public long longValue() { - return pgpKeyId; - } + public long longValue() + { + return pgpKeyId; + } - public String toHumanString() { - String s = toHumanString == null ? null : toHumanString.get(); - if (s == null) { - s = _toHumanString(); - toHumanString = new WeakReference(s); - } - return s; - } + public String toHumanString() + { + String s = toHumanString == null ? null : toHumanString.get(); + if (s == null) + { + s = _toHumanString(); + toHumanString = new WeakReference(s); + } + return s; + } - private String _toHumanString() { - final StringBuilder sb = new StringBuilder(); - final String string = toString(); + private String _toHumanString() + { + final StringBuilder sb = new StringBuilder(); + final String string = toString(); - for (int i = 0; i < string.length(); ++i) { - if (i > 0 && (i % 4 == 0)) - sb.append(' '); + for (int i = 0; i < string.length(); ++i) + { + if (i > 0 && (i % 4 == 0)) + sb.append(' '); - sb.append(string.charAt(i)); - } - return sb.toString(); - } + sb.append(string.charAt(i)); + } + return sb.toString(); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index fe2e4cc6f3..828379be32 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -35,471 +35,559 @@ /** * Registry providing fast access to the keys of a public and a secret key ring collection. *

- * An {@code PgpKeyRegistry} reads the {@code pubring.gpg} and {@code secring.gpg} - * (normally located in {@code ~/.gnupg/}) and organizes them in {@link PgpKey} instances. - * It then provides fast lookup by key-id or fingerprint via {@link #getPgpKey(PgpKeyId)} - * or {@link #getPgpKey(PgpKeyFingerprint)}. + * An {@code PgpKeyRegistry} reads the {@code pubring.gpg} and {@code secring.gpg} (normally located in + * {@code ~/.gnupg/}) and organizes them in {@link PgpKey} instances. It then provides fast lookup by key-id or + * fingerprint via {@link #getPgpKey(PgpKeyId)} or {@link #getPgpKey(PgpKeyFingerprint)}. *

- * The {@code PgpKeyRegistry} tracks the timestamps of the key ring collection files. If - * one of the files changes, i.e. the timestamp changes, the files are re-loaded. - * But beware: The file system's timestamps usually have a pretty bad resolution (of - * 1 or even 2 seconds). Therefore, it may happen that a modification goes undetected, - * if multiple changes occur within the resolution. + * The {@code PgpKeyRegistry} tracks the timestamps of the key ring collection files. If one of the files changes, i.e. + * the timestamp changes, the files are re-loaded. But beware: The file system's timestamps usually have a pretty bad + * resolution (of 1 or even 2 seconds). Therefore, it may happen that a modification goes undetected, if multiple + * changes occur within the resolution. * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpKeyRegistry { - private static final Logger logger = LoggerFactory.getLogger(PgpKeyRegistry.class); - - private final File pubringFile; - private final File secringFile; - - private long pubringFileLastModified = Long.MIN_VALUE; - private long secringFileLastModified = Long.MIN_VALUE; - - private Map pgpKeyFingerprint2pgpKey; // all keys - private Map pgpKeyId2pgpKey; // all keys - private Map pgpKeyId2masterKey; // only master-keys - - private Map> signingKeyId2signedKeyIds; - - /** - * Creates an instance of {@code PgpKeyRegistry} with the given public and secret key ring - * collection files. - * @param pubringFile the file containing the public keys - usually named {@code pubring.gpg} - * (located in {@code ~/.gnupg/}). Must not be null. The file does not need to exist, though. - * @param secringFile the file containing the secret keys - usually named {@code secring.gpg} - * (located in {@code ~/.gnupg/}). Must not be null. The file does not need to exist, though. - */ - public PgpKeyRegistry(File pubringFile, File secringFile) { - this.pubringFile = assertNotNull("pubringFile", pubringFile); - this.secringFile = assertNotNull("secringFile", secringFile); - } - - /** - * Gets the file containing the public keys - usually named {@code pubring.gpg} - * (located in {@code ~/.gnupg/}). - * @return the file containing the public keys. Never null. - */ - public File getPubringFile() { - return pubringFile; - } - - /** - * Gets the file containing the secret keys - usually named {@code secring.gpg} - * (located in {@code ~/.gnupg/}). - * @return the file containing the secret keys. Never null. - */ - public File getSecringFile() { - return secringFile; - } - - /** - * Gets the key with the given ID. If no such key exists, an {@link IllegalArgumentException} is thrown. - *

- * It makes no difference to this method whether the key is a master-key or a sub-key. - * @param pgpKeyId the key's ID. Must not be null. - * @return the key identified by the given {@code pgpKeyId}. Never null. - * @throws IllegalArgumentException if the given {@code pgpKeyId} is null or - * there is no key known with this ID. - */ - public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentException { - final PgpKey pgpKey = getPgpKey(pgpKeyId); - if (pgpKey == null) - throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); - - return pgpKey; - } - - /** - * Gets the key with the given ID. If no such key exists, null is returned. - *

- * It makes no difference to this method whether the key is a master-key or a sub-key. - * @param pgpKeyId the key's ID. Must not be null. - * @return the key identified by the given {@code pgpKeyId}. May be null. - * @throws IllegalArgumentException if the given {@code pgpKeyId} is null. - */ - public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException { - assertNotNull("pgpKeyId", pgpKeyId); - loadIfNeeded(); - final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); - return pgpKey; - } - - /** - * Gets the key with the given fingerprint. If no such key exists, an {@link IllegalArgumentException} is thrown. - *

- * It makes no difference to this method whether the key is a master-key or a sub-key. - * @param pgpKeyFingerprint the key's fingerprint. Must not be null. - * @return the key identified by the given {@code pgpKeyFingerprint}. Never null. - * @throws IllegalArgumentException if the given {@code pgpKeyFingerprint} is null or - * there is no key known with this fingerprint. - */ - public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException { - final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); - if (pgpKey == null) - throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); - - return pgpKey; - } - - /** - * Gets the key with the given fingerprint. If no such key exists, null is returned. - *

- * It makes no difference to this method whether the key is a master-key or a sub-key. - * @param pgpKeyFingerprint the key's fingerprint. Must not be null. - * @return the key identified by the given {@code pgpKeyFingerprint}. May be null. - * @throws IllegalArgumentException if the given {@code pgpKeyFingerprint} is null. - */ - public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException { - assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); - loadIfNeeded(); - final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); - return pgpKey; - } - - /** - * Gets all master-keys. Their sub-keys are accessible via {@link PgpKey#getSubKeys()}. - * @return all master-keys. Never null. - */ - public synchronized Collection getMasterKeys() { - loadIfNeeded(); - return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); - } - - /** - * Marks this registry stale - causing it to reload at the next read access. - *

- * If a modification of a key ring file happens, this modification is usually detected automatically, - * rendering this registry stale implicitly. However, a change is not reliably detected, because - * the file system's timestamp resolution is usually 1 second or even worse. Multiple changes within - * this resolution might thus go undetected. In order to make sure that a key ring file modification - * reliably causes this registry to reload, this method can be invoked. - */ - public void markStale() { - pubringFileLastModified = Long.MIN_VALUE; - secringFileLastModified = Long.MIN_VALUE; - } - - /** - * Loads the key ring files, if they were not yet read or if this registry is stale. - */ - protected synchronized void loadIfNeeded() { - if (pgpKeyId2pgpKey == null - || getPubringFile().lastModified() != pubringFileLastModified - || getSecringFile().lastModified() != secringFileLastModified) { - logger.debug("loadIfNeeded: invoking load()."); - load(); - } - else - logger.trace("loadIfNeeded: *not* invoking load()."); - } - - /** - * Loads the key ring files. - */ - protected synchronized void load() { - pgpKeyFingerprint2pgpKey = null; - final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); - final Map pgpKeyId2pgpKey = new HashMap<>(); - final Map pgpKeyId2masterKey = new HashMap<>(); - - final long pubringFileLastModified; - final long secringFileLastModified; - try { - final File secringFile = getSecringFile(); - logger.debug("load: secringFile='{}'", secringFile); - secringFileLastModified = secringFile.lastModified(); - if (secringFile.isFile()) { - final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; - try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) { - pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); - } - for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext(); ) { - final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); - PgpKey masterKey = null; - for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext(); ) { - final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); - masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, - pgpKeyId2masterKey, masterKey, keyRing, publicKey); - } - - for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext(); ) { - final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); - final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); - final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); - if (pgpKey == null) - throw new IllegalStateException("Secret key does not have corresponding public key in secret key ring! pgpKeyId=" + pgpKeyId); - - pgpKey.setSecretKey(secretKey); - logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); - } - } - } - - final File pubringFile = getPubringFile(); - logger.debug("load: pubringFile='{}'", pubringFile); - pubringFileLastModified = pubringFile.lastModified(); - if (pubringFile.isFile()) { - final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; - try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) { - pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), new BcKeyFingerprintCalculator()); - } - - for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext(); ) { - final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); - PgpKey masterKey = null; - for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext(); ) { - final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); - masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, - pgpKeyId2masterKey, masterKey, keyRing, publicKey); - } - } - } - } catch (IOException | PGPException x) { - throw new RuntimeException(x); - } - - for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) { - if (pgpKey.getPublicKey() == null) - throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); - - if (pgpKey.getPublicKeyRing() == null) - throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); - } - - this.secringFileLastModified = secringFileLastModified; - this.pubringFileLastModified = pubringFileLastModified; - this.pgpKeyFingerprint2pgpKey = pgpKeyFingerprint2pgpKey; - this.pgpKeyId2pgpKey = pgpKeyId2pgpKey; - this.pgpKeyId2masterKey = pgpKeyId2masterKey; - - assignSubKeys(); - } - - private void assignSubKeys() { - for (final PgpKey masterKey : pgpKeyId2masterKey.values()) { - final Set subKeyIds = masterKey.getSubKeyIds(); - final List subKeys = new ArrayList(subKeyIds.size()); - for (final PgpKeyId subKeyId : subKeyIds) { - final PgpKey subKey = getPgpKeyOrFail(subKeyId); - subKeys.add(subKey); - } - masterKey.setSubKeys(Collections.unmodifiableList(subKeys)); - masterKey.setSubKeyIds(Collections.unmodifiableSet(subKeyIds)); - } - } - - private PgpKey enlistPublicKey(final Map pgpKeyFingerprint2pgpKey, - final Map pgpKeyId2PgpKey, - final Map pgpKeyId2masterKey, - PgpKey masterKey, final PGPKeyRing keyRing, final PGPPublicKey publicKey) - { - final PgpKeyId pgpKeyId = new PgpKeyId(publicKey.getKeyID()); - final PgpKeyFingerprint pgpKeyFingerprint = new PgpKeyFingerprint(publicKey.getFingerprint()); - - PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); - if (pgpKey == null) { - pgpKey = new PgpKey(pgpKeyId, pgpKeyFingerprint); - pgpKeyFingerprint2pgpKey.put(pgpKeyFingerprint, pgpKey); - PgpKey old = pgpKeyId2PgpKey.put(pgpKeyId, pgpKey); - if (old != null) - throw new IllegalStateException(String.format("PGP-key-ID collision! Two keys with different fingerprints have the same key-ID! keyId=%s fingerprint1=%s fingerprint2=%s", - pgpKeyId, old.getPgpKeyFingerprint(), pgpKey.getPgpKeyFingerprint())); - } - - if (keyRing instanceof PGPSecretKeyRing) - pgpKey.setSecretKeyRing((PGPSecretKeyRing)keyRing); - else if (keyRing instanceof PGPPublicKeyRing) - pgpKey.setPublicKeyRing((PGPPublicKeyRing)keyRing); - else - throw new IllegalArgumentException("keyRing is neither an instance of PGPSecretKeyRing nor PGPPublicKeyRing!"); - - pgpKey.setPublicKey(publicKey); - - if (publicKey.isMasterKey()) { - masterKey = pgpKey; - pgpKeyId2masterKey.put(pgpKey.getPgpKeyId(), pgpKey); - } - else { - if (masterKey == null) - throw new IllegalStateException("First key is a non-master key!"); - - pgpKey.setMasterKey(masterKey); - masterKey.getSubKeyIds().add(pgpKey.getPgpKeyId()); - } - return masterKey; - } - - /** - * Gets all those keys' fingerprints whose keys were signed (certified) by the key identified by the given fingerprint. - *

- * Usually, the fingerprint specified should identify a master-key and usually only master-key-fingerprints are - * returned by this method. - * @param signingPgpKeyFingerprint the fingerprint of the key having signed all those keys that we're interested in. - * Must not be null. - * @return the fingerprints of all those keys which have been signed (certified) by the key identified by - * {@code signingPgpKeyFingerprint}. Never null, but maybe empty. - */ - public synchronized Set getPgpKeyFingerprintsSignedBy(final PgpKeyFingerprint signingPgpKeyFingerprint) { - assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); - final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); - if (signingPgpKey == null) - return Collections.emptySet(); - - final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); - if (pgpKeyIds == null) - return Collections.emptySet(); - - final Set result = new HashSet<>(pgpKeyIds.size()); - for (final PgpKeyId pgpKeyId : pgpKeyIds) { - final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); - result.add(pgpKey.getPgpKeyFingerprint()); - } - return Collections.unmodifiableSet(result); - } - - /** - * Gets all those keys' IDs whose keys were signed (certified) by the key identified by the given ID. - *

- * Usually, the ID specified should identify a master-key and usually only master-key-IDs are - * returned by this method. - * @param signingPgpKeyId the ID of the key having signed all those keys that we're interested in. - * Must not be null. - * @return the IDs of all those keys which have been signed (certified) by the key identified by - * {@code signingPgpKeyId}. Never null, but maybe empty. - */ - public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) { - final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); - if (pgpKeyIds == null) - return Collections.emptySet(); - - return Collections.unmodifiableSet(pgpKeyIds); - } - - @SuppressWarnings("unchecked") - protected synchronized Map> getSigningKeyId2signedKeyIds() { - loadIfNeeded(); - if (signingKeyId2signedKeyIds == null) { - final Map> m = new HashMap<>(); - for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) { - final PGPPublicKey publicKey = pgpKey.getPublicKey(); - for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { - if (pgpUserId.getUserId() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext(); ) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } - else if (pgpUserId.getUserAttribute() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext(); ) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } - else - throw new IllegalStateException("WTF?!"); - } - - // It seems, there are both: certifications for individual user-ids and certifications for the - // entire key. I therefore first take the individual ones (above) into account then and then - // the ones for the entire key (below). - for (Iterator it = nullToEmpty(publicKey.getSignatures()); it.hasNext(); ) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } - signingKeyId2signedKeyIds = m; - } - return signingKeyId2signedKeyIds; - } - - /** - * Gets the signatures certifying the authenticity of the given user-ID. - * @param pgpUserId the user-ID whose certifications should be returned. Must not be null. - * @return the certifications authenticating the given {@code pgpUserId}. Never null. - * Because every user-ID is normally at least signed by the owning key, it is normally never empty, too. - */ - @SuppressWarnings("unchecked") - public synchronized List getSignatures(final PgpUserId pgpUserId) { - assertNotNull("pgpUserId", pgpUserId); - final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); - - final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); - - final List result = new ArrayList<>(); - if (pgpUserId.getUserId() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext(); ) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); - } - } - } - else if (pgpUserId.getUserAttribute() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext(); ) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); - } - } - } - else - throw new IllegalStateException("WTF?!"); - - return result; - } - - protected static Iterator nullToEmpty(final Iterator iterator) { - if (iterator == null) - return Collections.emptyList().iterator(); - else - return iterator; - } - - private void enlistInSigningKey2signedKeyIds(final Map> signingKeyId2signedKeyIds, - final PgpKey pgpKey, final PGPSignature pgpSignature) { - final PgpKeyId signingPgpKeyId = new PgpKeyId(pgpSignature.getKeyID()); - Set signedKeyIds = signingKeyId2signedKeyIds.get(signingPgpKeyId); - if (signedKeyIds == null) { - signedKeyIds = new HashSet<>(); - signingKeyId2signedKeyIds.put(signingPgpKeyId, signedKeyIds); - } - signedKeyIds.add(pgpKey.getPgpKeyId()); - } - - /** - * Determines whether the given signature is a certification. - *

- * A certification is a signature indicating that a certain key or user-identity is authentic. - * @param pgpSignature the signature to be checked. Must not be null. - * @return true, if the signature is a certification; false, if it is - * of a different type. - * @see #isCertification(int) - */ - public boolean isCertification(final PGPSignature pgpSignature) { - assertNotNull("pgpSignature", pgpSignature); - return isCertification(pgpSignature.getSignatureType()); - } - - /** - * Determines whether the given signature-type indicates a certification. - *

- * A certification is a signature indicating that a certain key or user-identity is authentic. - * @param pgpSignatureType the type of the signature - like {@link PGPSignature#DEFAULT_CERTIFICATION} - * or other constants (used by the property {@link PGPSignature#getSignatureType()}, for example). - * @return true, if the given signature-type means certification; false otherwise. - * @see #isCertification(PGPSignature) - */ - public boolean isCertification(int pgpSignatureType) { - return PGPSignature.DEFAULT_CERTIFICATION == pgpSignatureType - || PGPSignature.NO_CERTIFICATION == pgpSignatureType - || PGPSignature.CASUAL_CERTIFICATION == pgpSignatureType - || PGPSignature.POSITIVE_CERTIFICATION == pgpSignatureType; - } +public class PgpKeyRegistry +{ + private static final Logger logger = LoggerFactory.getLogger(PgpKeyRegistry.class); + + private final File pubringFile; + private final File secringFile; + + private long pubringFileLastModified = Long.MIN_VALUE; + private long secringFileLastModified = Long.MIN_VALUE; + + private Map pgpKeyFingerprint2pgpKey; // all keys + private Map pgpKeyId2pgpKey; // all keys + private Map pgpKeyId2masterKey; // only master-keys + + private Map> signingKeyId2signedKeyIds; + + /** + * Creates an instance of {@code PgpKeyRegistry} with the given public and secret key ring collection files. + * + * @param pubringFile + * the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/} + * ). Must not be null. The file does not need to exist, though. + * @param secringFile + * the file containing the secret keys - usually named {@code secring.gpg} (located in {@code ~/.gnupg/} + * ). Must not be null. The file does not need to exist, though. + */ + public PgpKeyRegistry(File pubringFile, File secringFile) + { + this.pubringFile = assertNotNull("pubringFile", pubringFile); + this.secringFile = assertNotNull("secringFile", secringFile); + } + + /** + * Gets the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/}). + * + * @return the file containing the public keys. Never null. + */ + public File getPubringFile() + { + return pubringFile; + } + + /** + * Gets the file containing the secret keys - usually named {@code secring.gpg} (located in {@code ~/.gnupg/}). + * + * @return the file containing the secret keys. Never null. + */ + public File getSecringFile() + { + return secringFile; + } + + /** + * Gets the key with the given ID. If no such key exists, an {@link IllegalArgumentException} is thrown. + *

+ * It makes no difference to this method whether the key is a master-key or a sub-key. + * + * @param pgpKeyId + * the key's ID. Must not be null. + * @return the key identified by the given {@code pgpKeyId}. Never null. + * @throws IllegalArgumentException + * if the given {@code pgpKeyId} is null or there is no key known with this ID. + */ + public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentException + { + final PgpKey pgpKey = getPgpKey(pgpKeyId); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); + + return pgpKey; + } + + /** + * Gets the key with the given ID. If no such key exists, null is returned. + *

+ * It makes no difference to this method whether the key is a master-key or a sub-key. + * + * @param pgpKeyId + * the key's ID. Must not be null. + * @return the key identified by the given {@code pgpKeyId}. May be null. + * @throws IllegalArgumentException + * if the given {@code pgpKeyId} is null. + */ + public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException + { + assertNotNull("pgpKeyId", pgpKeyId); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + return pgpKey; + } + + /** + * Gets the key with the given fingerprint. If no such key exists, an {@link IllegalArgumentException} is thrown. + *

+ * It makes no difference to this method whether the key is a master-key or a sub-key. + * + * @param pgpKeyFingerprint + * the key's fingerprint. Must not be null. + * @return the key identified by the given {@code pgpKeyFingerprint}. Never null. + * @throws IllegalArgumentException + * if the given {@code pgpKeyFingerprint} is null or there is no key known with this + * fingerprint. + */ + public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException + { + final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); + + return pgpKey; + } + + /** + * Gets the key with the given fingerprint. If no such key exists, null is returned. + *

+ * It makes no difference to this method whether the key is a master-key or a sub-key. + * + * @param pgpKeyFingerprint + * the key's fingerprint. Must not be null. + * @return the key identified by the given {@code pgpKeyFingerprint}. May be null. + * @throws IllegalArgumentException + * if the given {@code pgpKeyFingerprint} is null. + */ + public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException + { + assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + return pgpKey; + } + + /** + * Gets all master-keys. Their sub-keys are accessible via {@link PgpKey#getSubKeys()}. + * + * @return all master-keys. Never null. + */ + public synchronized Collection getMasterKeys() + { + loadIfNeeded(); + return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); + } + + /** + * Marks this registry stale - causing it to reload at the next read access. + *

+ * If a modification of a key ring file happens, this modification is usually detected automatically, rendering this + * registry stale implicitly. However, a change is not reliably detected, because the file system's timestamp + * resolution is usually 1 second or even worse. Multiple changes within this resolution might thus go undetected. + * In order to make sure that a key ring file modification reliably causes this registry to reload, this method can + * be invoked. + */ + public void markStale() + { + pubringFileLastModified = Long.MIN_VALUE; + secringFileLastModified = Long.MIN_VALUE; + } + + /** + * Loads the key ring files, if they were not yet read or if this registry is stale. + */ + protected synchronized void loadIfNeeded() + { + if (pgpKeyId2pgpKey == null + || getPubringFile().lastModified() != pubringFileLastModified + || getSecringFile().lastModified() != secringFileLastModified) + { + logger.debug("loadIfNeeded: invoking load()."); + load(); + } + else + logger.trace("loadIfNeeded: *not* invoking load()."); + } + + /** + * Loads the key ring files. + */ + protected synchronized void load() + { + pgpKeyFingerprint2pgpKey = null; + final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); + final Map pgpKeyId2pgpKey = new HashMap<>(); + final Map pgpKeyId2masterKey = new HashMap<>(); + + final long pubringFileLastModified; + final long secringFileLastModified; + try + { + final File secringFile = getSecringFile(); + logger.debug("load: secringFile='{}'", secringFile); + secringFileLastModified = secringFile.lastModified(); + if (secringFile.isFile()) + { + final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) + { + pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), + new BcKeyFingerprintCalculator()); + } + for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext();) + { + final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + + for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext();) + { + final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); + final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + if (pgpKey == null) + throw new IllegalStateException( + "Secret key does not have corresponding public key in secret key ring! pgpKeyId=" + + pgpKeyId); + + pgpKey.setSecretKey(secretKey); + logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); + } + } + } + + final File pubringFile = getPubringFile(); + logger.debug("load: pubringFile='{}'", pubringFile); + pubringFileLastModified = pubringFile.lastModified(); + if (pubringFile.isFile()) + { + final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) + { + pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), + new BcKeyFingerprintCalculator()); + } + + for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext();) + { + final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + } + } + } catch (IOException | PGPException x) + { + throw new RuntimeException(x); + } + + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) + { + if (pgpKey.getPublicKey() == null) + throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); + + if (pgpKey.getPublicKeyRing() == null) + throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); + } + + this.secringFileLastModified = secringFileLastModified; + this.pubringFileLastModified = pubringFileLastModified; + this.pgpKeyFingerprint2pgpKey = pgpKeyFingerprint2pgpKey; + this.pgpKeyId2pgpKey = pgpKeyId2pgpKey; + this.pgpKeyId2masterKey = pgpKeyId2masterKey; + + assignSubKeys(); + } + + private void assignSubKeys() + { + for (final PgpKey masterKey : pgpKeyId2masterKey.values()) + { + final Set subKeyIds = masterKey.getSubKeyIds(); + final List subKeys = new ArrayList(subKeyIds.size()); + for (final PgpKeyId subKeyId : subKeyIds) + { + final PgpKey subKey = getPgpKeyOrFail(subKeyId); + subKeys.add(subKey); + } + masterKey.setSubKeys(Collections.unmodifiableList(subKeys)); + masterKey.setSubKeyIds(Collections.unmodifiableSet(subKeyIds)); + } + } + + private PgpKey enlistPublicKey(final Map pgpKeyFingerprint2pgpKey, + final Map pgpKeyId2PgpKey, + final Map pgpKeyId2masterKey, + PgpKey masterKey, final PGPKeyRing keyRing, final PGPPublicKey publicKey) + { + final PgpKeyId pgpKeyId = new PgpKeyId(publicKey.getKeyID()); + final PgpKeyFingerprint pgpKeyFingerprint = new PgpKeyFingerprint(publicKey.getFingerprint()); + + PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + if (pgpKey == null) + { + pgpKey = new PgpKey(pgpKeyId, pgpKeyFingerprint); + pgpKeyFingerprint2pgpKey.put(pgpKeyFingerprint, pgpKey); + PgpKey old = pgpKeyId2PgpKey.put(pgpKeyId, pgpKey); + if (old != null) + throw new IllegalStateException( + String.format( + "PGP-key-ID collision! Two keys with different fingerprints have the same key-ID! keyId=%s fingerprint1=%s fingerprint2=%s", + pgpKeyId, old.getPgpKeyFingerprint(), pgpKey.getPgpKeyFingerprint())); + } + + if (keyRing instanceof PGPSecretKeyRing) + pgpKey.setSecretKeyRing((PGPSecretKeyRing) keyRing); + else if (keyRing instanceof PGPPublicKeyRing) + pgpKey.setPublicKeyRing((PGPPublicKeyRing) keyRing); + else + throw new IllegalArgumentException( + "keyRing is neither an instance of PGPSecretKeyRing nor PGPPublicKeyRing!"); + + pgpKey.setPublicKey(publicKey); + + if (publicKey.isMasterKey()) + { + masterKey = pgpKey; + pgpKeyId2masterKey.put(pgpKey.getPgpKeyId(), pgpKey); + } + else + { + if (masterKey == null) + throw new IllegalStateException("First key is a non-master key!"); + + pgpKey.setMasterKey(masterKey); + masterKey.getSubKeyIds().add(pgpKey.getPgpKeyId()); + } + return masterKey; + } + + /** + * Gets all those keys' fingerprints whose keys were signed (certified) by the key identified by the given + * fingerprint. + *

+ * Usually, the fingerprint specified should identify a master-key and usually only master-key-fingerprints are + * returned by this method. + * + * @param signingPgpKeyFingerprint + * the fingerprint of the key having signed all those keys that we're interested in. Must not be + * null. + * @return the fingerprints of all those keys which have been signed (certified) by the key identified by + * {@code signingPgpKeyFingerprint}. Never null, but maybe empty. + */ + public synchronized Set getPgpKeyFingerprintsSignedBy( + final PgpKeyFingerprint signingPgpKeyFingerprint) + { + assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); + final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); + if (signingPgpKey == null) + return Collections.emptySet(); + + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); + if (pgpKeyIds == null) + return Collections.emptySet(); + + final Set result = new HashSet<>(pgpKeyIds.size()); + for (final PgpKeyId pgpKeyId : pgpKeyIds) + { + final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); + result.add(pgpKey.getPgpKeyFingerprint()); + } + return Collections.unmodifiableSet(result); + } + + /** + * Gets all those keys' IDs whose keys were signed (certified) by the key identified by the given ID. + *

+ * Usually, the ID specified should identify a master-key and usually only master-key-IDs are returned by this + * method. + * + * @param signingPgpKeyId + * the ID of the key having signed all those keys that we're interested in. Must not be null + * . + * @return the IDs of all those keys which have been signed (certified) by the key identified by + * {@code signingPgpKeyId}. Never null, but maybe empty. + */ + public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) + { + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); + if (pgpKeyIds == null) + return Collections.emptySet(); + + return Collections.unmodifiableSet(pgpKeyIds); + } + + @SuppressWarnings("unchecked") + protected synchronized Map> getSigningKeyId2signedKeyIds() + { + loadIfNeeded(); + if (signingKeyId2signedKeyIds == null) + { + final Map> m = new HashMap<>(); + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) + { + final PGPPublicKey publicKey = pgpKey.getPublicKey(); + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) + { + if (pgpUserId.getUserId() != null) + { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it + .hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + else if (pgpUserId.getUserAttribute() != null) + { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId + .getUserAttribute())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + else + throw new IllegalStateException("WTF?!"); + } + + // It seems, there are both: certifications for individual user-ids and certifications for the + // entire key. I therefore first take the individual ones (above) into account then and then + // the ones for the entire key (below). + for (Iterator it = nullToEmpty(publicKey.getSignatures()); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + signingKeyId2signedKeyIds = m; + } + return signingKeyId2signedKeyIds; + } + + /** + * Gets the signatures certifying the authenticity of the given user-ID. + * + * @param pgpUserId + * the user-ID whose certifications should be returned. Must not be null. + * @return the certifications authenticating the given {@code pgpUserId}. Never null. Because every + * user-ID is normally at least signed by the owning key, it is normally never empty, too. + */ + @SuppressWarnings("unchecked") + public synchronized List getSignatures(final PgpUserId pgpUserId) + { + assertNotNull("pgpUserId", pgpUserId); + final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); + + final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); + + final List result = new ArrayList<>(); + if (pgpUserId.getUserId() != null) + { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else if (pgpUserId.getUserAttribute() != null) + { + for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId + .getUserAttribute())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else + throw new IllegalStateException("WTF?!"); + + return result; + } + + protected static Iterator nullToEmpty(final Iterator iterator) + { + if (iterator == null) + return Collections. emptyList().iterator(); + else + return iterator; + } + + private void enlistInSigningKey2signedKeyIds(final Map> signingKeyId2signedKeyIds, + final PgpKey pgpKey, final PGPSignature pgpSignature) + { + final PgpKeyId signingPgpKeyId = new PgpKeyId(pgpSignature.getKeyID()); + Set signedKeyIds = signingKeyId2signedKeyIds.get(signingPgpKeyId); + if (signedKeyIds == null) + { + signedKeyIds = new HashSet<>(); + signingKeyId2signedKeyIds.put(signingPgpKeyId, signedKeyIds); + } + signedKeyIds.add(pgpKey.getPgpKeyId()); + } + + /** + * Determines whether the given signature is a certification. + *

+ * A certification is a signature indicating that a certain key or user-identity is authentic. + * + * @param pgpSignature + * the signature to be checked. Must not be null. + * @return true, if the signature is a certification; false, if it is of a different type. + * @see #isCertification(int) + */ + public boolean isCertification(final PGPSignature pgpSignature) + { + assertNotNull("pgpSignature", pgpSignature); + return isCertification(pgpSignature.getSignatureType()); + } + + /** + * Determines whether the given signature-type indicates a certification. + *

+ * A certification is a signature indicating that a certain key or user-identity is authentic. + * + * @param pgpSignatureType + * the type of the signature - like {@link PGPSignature#DEFAULT_CERTIFICATION} or other constants (used + * by the property {@link PGPSignature#getSignatureType()}, for example). + * @return true, if the given signature-type means certification; false otherwise. + * @see #isCertification(PGPSignature) + */ + public boolean isCertification(int pgpSignatureType) + { + return PGPSignature.DEFAULT_CERTIFICATION == pgpSignatureType + || PGPSignature.NO_CERTIFICATION == pgpSignatureType + || PGPSignature.CASUAL_CERTIFICATION == pgpSignatureType + || PGPSignature.POSITIVE_CERTIFICATION == pgpSignatureType; + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java index 9e5363b14c..eea3ef9c54 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java @@ -6,52 +6,62 @@ /** * User-identity or user-attribute of an OpenPGP key. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpUserId { - private final PgpKey pgpKey; - private final String userId; - private final PGPUserAttributeSubpacketVector userAttribute; - private PgpUserIdNameHash nameHash; - - public PgpUserId(final PgpKey pgpKey, final String userId) { - this.pgpKey = assertNotNull("pgpKey", pgpKey); - this.userId = assertNotNull("userId", userId); - this.userAttribute = null; - } - - public PgpUserId(final PgpKey pgpKey, final PGPUserAttributeSubpacketVector userAttribute) { - this.pgpKey = assertNotNull("pgpKey", pgpKey); - this.userId = null; - this.userAttribute = assertNotNull("userAttribute", userAttribute); - } - - public PgpKey getPgpKey() { - return pgpKey; - } - - public String getUserId() { - return userId; - } - - public PGPUserAttributeSubpacketVector getUserAttribute() { - return userAttribute; - } - - // namehash_from_uid (PKT_user_id *uid) from keyid.c - public PgpUserIdNameHash getNameHash() { - if (nameHash == null) { - if (userId != null) - nameHash = PgpUserIdNameHash.createFromUserId(userId); - else - nameHash = PgpUserIdNameHash.createFromUserAttribute(userAttribute); - } - return nameHash; - } - - @Override - public String toString() { - return String.format("%s[pgpKeyId=%s userId=%s userAttribute=%s]", - this.getClass().getSimpleName(), getPgpKey().getPgpKeyId(), userId, userAttribute); - } +public class PgpUserId +{ + private final PgpKey pgpKey; + private final String userId; + private final PGPUserAttributeSubpacketVector userAttribute; + private PgpUserIdNameHash nameHash; + + public PgpUserId(final PgpKey pgpKey, final String userId) + { + this.pgpKey = assertNotNull("pgpKey", pgpKey); + this.userId = assertNotNull("userId", userId); + this.userAttribute = null; + } + + public PgpUserId(final PgpKey pgpKey, final PGPUserAttributeSubpacketVector userAttribute) + { + this.pgpKey = assertNotNull("pgpKey", pgpKey); + this.userId = null; + this.userAttribute = assertNotNull("userAttribute", userAttribute); + } + + public PgpKey getPgpKey() + { + return pgpKey; + } + + public String getUserId() + { + return userId; + } + + public PGPUserAttributeSubpacketVector getUserAttribute() + { + return userAttribute; + } + + // namehash_from_uid (PKT_user_id *uid) from keyid.c + public PgpUserIdNameHash getNameHash() + { + if (nameHash == null) + { + if (userId != null) + nameHash = PgpUserIdNameHash.createFromUserId(userId); + else + nameHash = PgpUserIdNameHash.createFromUserAttribute(userAttribute); + } + return nameHash; + } + + @Override + public String toString() + { + return String.format("%s[pgpKeyId=%s userId=%s userAttribute=%s]", + this.getClass().getSimpleName(), getPgpKey().getPgpKeyId(), userId, userAttribute); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java index 6352df5e0b..04f388ee35 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java @@ -16,145 +16,172 @@ /** * Hash used as identifier of a user-identity or user-attribute. *

- * Use {@link #createFromUserId(String)} or {@link #createFromUserAttribute(PGPUserAttributeSubpacketVector)} - * to create an instance. + * Use {@link #createFromUserId(String)} or {@link #createFromUserAttribute(PGPUserAttributeSubpacketVector)} to create + * an instance. + * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpUserIdNameHash implements Comparable, Serializable { - private static final long serialVersionUID = 1L; - - private final byte[] namehash; - private transient int hashCode; - private transient WeakReference toString; - private transient WeakReference toHumanString; - - protected PgpUserIdNameHash(final byte[] namehash) { - assertNotNull("namehash", namehash); - this.namehash = namehash; - } - - public PgpUserIdNameHash(final String namehash) { - assertNotNull("namehash", namehash); - this.namehash = decodeHexStr(namehash); - } - - public byte[] getBytes() { - // In order to guarantee that this instance stays immutable, we copy the byte array. - return copyOf(namehash, namehash.length); - } - - @Override - public int hashCode() { - if (hashCode == 0) - hashCode = Arrays.hashCode(namehash); - - return hashCode; - } - - public boolean equals(final byte[] namehash) { - if (namehash == null) - return false; - - return Arrays.equals(this.namehash, namehash); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - - if (obj instanceof byte[]) - return equals((byte[]) obj); - - if (getClass() != obj.getClass()) return false; - - final PgpUserIdNameHash other = (PgpUserIdNameHash) obj; - return Arrays.equals(namehash, other.namehash); - } - - @Override - public int compareTo(PgpUserIdNameHash o) { - int res = Integer.compare(this.namehash.length, o.namehash.length); - if (res != 0) - return res; - - for (int i = 0; i < this.namehash.length; i++) { - res = Byte.compare(this.namehash[i], o.namehash[i]); - if (res != 0) - return res; - } - return 0; - } - - @Override - public String toString() { - String s = toString == null ? null : toString.get(); - if (s == null) { - s = encodeHexStr(namehash); - toString = new WeakReference(s); - } - return s; - } - - public String toHumanString() { - String s = toHumanString == null ? null : toHumanString.get(); - if (s == null) { - s = _toHumanString(); - toHumanString = new WeakReference(s); - } - return s; - } - - private String _toHumanString() { - final StringBuilder sb = new StringBuilder(); - final String string = toString(); - - for (int i = 0; i < string.length(); ++i) { - if (i > 0 && (i % 4 == 0)) - sb.append(' '); - - sb.append(string.charAt(i)); - } - return sb.toString(); - } - - /** - * Creates an instance of {@code PgpUserIdNameHash} for the given user-identity. - * @param userId the user-identity for which to create a name-hash instance. Must not be null. - * @return the name-hash. Never null. - */ - public static PgpUserIdNameHash createFromUserId(final String userId) { - assertNotNull("userId", userId); - - final RIPEMD160Digest digest = new RIPEMD160Digest(); - byte[] userIdBytes = userId.getBytes(StandardCharsets.UTF_8); // TODO is this correct?! really UTF-8?! check with my own name! ;-) - digest.update(userIdBytes, 0, userIdBytes.length); - final byte[] out = new byte[digest.getDigestSize()]; - digest.doFinal(out, 0); - - return new PgpUserIdNameHash(out); - } - - /** - * Creates an instance of {@code PgpUserIdNameHash} for the given user-attribute. A user-attribute usually - * is an image. - * @param userAttribute the user-attribute for which to create a name-hash instance. Must not be null. - * @return the name-hash. Never null. - */ - public static PgpUserIdNameHash createFromUserAttribute(final PGPUserAttributeSubpacketVector userAttribute) { - assertNotNull("userAttribute", userAttribute); - - final RIPEMD160Digest digest = new RIPEMD160Digest(); - - // TODO this needs to be extended, if there is ever any other attribute type (other than image) possible, too! - // Currently, image seems to be the only supported attribute. Alternatively, we could get the data via reflection... - final UserAttributeSubpacket subpacket = userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE); - assertNotNull("subpacket", subpacket); - final byte[] data = assertNotNull("subpacket.data", subpacket.getData()); - digest.update(data, 0, data.length); - - final byte[] out = new byte[digest.getDigestSize()]; - digest.doFinal(out, 0); - return new PgpUserIdNameHash(out); - } +public class PgpUserIdNameHash implements Comparable, Serializable +{ + private static final long serialVersionUID = 1L; + + private final byte[] namehash; + private transient int hashCode; + private transient WeakReference toString; + private transient WeakReference toHumanString; + + protected PgpUserIdNameHash(final byte[] namehash) + { + assertNotNull("namehash", namehash); + this.namehash = namehash; + } + + public PgpUserIdNameHash(final String namehash) + { + assertNotNull("namehash", namehash); + this.namehash = decodeHexStr(namehash); + } + + public byte[] getBytes() + { + // In order to guarantee that this instance stays immutable, we copy the byte array. + return copyOf(namehash, namehash.length); + } + + @Override + public int hashCode() + { + if (hashCode == 0) + hashCode = Arrays.hashCode(namehash); + + return hashCode; + } + + public boolean equals(final byte[] namehash) + { + if (namehash == null) + return false; + + return Arrays.equals(this.namehash, namehash); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + + if (obj instanceof byte[]) + return equals((byte[]) obj); + + if (getClass() != obj.getClass()) + return false; + + final PgpUserIdNameHash other = (PgpUserIdNameHash) obj; + return Arrays.equals(namehash, other.namehash); + } + + @Override + public int compareTo(PgpUserIdNameHash o) + { + int res = Integer.compare(this.namehash.length, o.namehash.length); + if (res != 0) + return res; + + for (int i = 0; i < this.namehash.length; i++) + { + res = Byte.compare(this.namehash[i], o.namehash[i]); + if (res != 0) + return res; + } + return 0; + } + + @Override + public String toString() + { + String s = toString == null ? null : toString.get(); + if (s == null) + { + s = encodeHexStr(namehash); + toString = new WeakReference(s); + } + return s; + } + + public String toHumanString() + { + String s = toHumanString == null ? null : toHumanString.get(); + if (s == null) + { + s = _toHumanString(); + toHumanString = new WeakReference(s); + } + return s; + } + + private String _toHumanString() + { + final StringBuilder sb = new StringBuilder(); + final String string = toString(); + + for (int i = 0; i < string.length(); ++i) + { + if (i > 0 && (i % 4 == 0)) + sb.append(' '); + + sb.append(string.charAt(i)); + } + return sb.toString(); + } + + /** + * Creates an instance of {@code PgpUserIdNameHash} for the given user-identity. + * + * @param userId + * the user-identity for which to create a name-hash instance. Must not be null. + * @return the name-hash. Never null. + */ + public static PgpUserIdNameHash createFromUserId(final String userId) + { + assertNotNull("userId", userId); + + final RIPEMD160Digest digest = new RIPEMD160Digest(); + byte[] userIdBytes = userId.getBytes(StandardCharsets.UTF_8); // TODO is this correct?! really UTF-8?! check + // with my own name! ;-) + digest.update(userIdBytes, 0, userIdBytes.length); + final byte[] out = new byte[digest.getDigestSize()]; + digest.doFinal(out, 0); + + return new PgpUserIdNameHash(out); + } + + /** + * Creates an instance of {@code PgpUserIdNameHash} for the given user-attribute. A user-attribute usually is an + * image. + * + * @param userAttribute + * the user-attribute for which to create a name-hash instance. Must not be null. + * @return the name-hash. Never null. + */ + public static PgpUserIdNameHash createFromUserAttribute(final PGPUserAttributeSubpacketVector userAttribute) + { + assertNotNull("userAttribute", userAttribute); + + final RIPEMD160Digest digest = new RIPEMD160Digest(); + + // TODO this needs to be extended, if there is ever any other attribute type (other than image) possible, too! + // Currently, image seems to be the only supported attribute. Alternatively, we could get the data via + // reflection... + final UserAttributeSubpacket subpacket = userAttribute.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE); + assertNotNull("subpacket", subpacket); + final byte[] data = assertNotNull("subpacket.data", subpacket.getData()); + digest.update(data, 0, data.length); + + final byte[] out = new byte[digest.getDigestSize()]; + digest.doFinal(out, 0); + return new PgpUserIdNameHash(out); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java index 65e9676929..a2f0c6213e 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java @@ -4,3 +4,4 @@ * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.TrustDb TrustDb}. */ package org.bouncycastle.openpgp.wot; + From 7d03dadae091f8c6df3891d7ffce30c014895b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 16:46:07 +0700 Subject: [PATCH 05/17] Taking key-signatures into account (additionally to user-id-signatures). --- .../openpgp/wot/key/PgpKeyRegistry.java | 16 +++++++++++++++- .../openpgp/wot/TrustDbProductiveFileTest.java | 3 +-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index 828379be32..15a3a09a86 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -475,7 +475,9 @@ else if (pgpUserId.getUserAttribute() != null) // It seems, there are both: certifications for individual user-ids and certifications for the // entire key. I therefore first take the individual ones (above) into account then and then // the ones for the entire key (below). - for (Iterator it = nullToEmpty(publicKey.getSignatures()); it.hasNext();) + // Normally, the signatures bound to the key are never 'certifications', but it rarely happens. + // Don't know, if these are malformed or deprecated (very old) keys, but I should take them into account. + for (Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (isCertification(pgpSignature)) @@ -532,6 +534,18 @@ else if (pgpUserId.getUserAttribute() != null) else throw new IllegalStateException("WTF?!"); + // There are also key-signatures which are not for a certain indivdual user-id/-attribute, but for the entire key. + // See the comment in getSigningKeyId2signedKeyIds() above for more details. + for (final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + return result; } diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java index 95d648bc89..2a07b91b8e 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java @@ -17,12 +17,11 @@ import org.bouncycastle.openpgp.wot.key.PgpKey; import org.bouncycastle.openpgp.wot.key.PgpKeyId; import org.bouncycastle.openpgp.wot.key.PgpUserId; -import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Ignore("This test is for playing around while developing - it's not a regular test!") +//@Ignore("This test is for playing around while developing - it's not a regular test!") public class TrustDbProductiveFileTest extends AbstractTrustDbTest { private static final Logger logger = LoggerFactory.getLogger(TrustDbProductiveFileTest.class); From ba1318c8037514d765f565dd23a9754466bd6ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 16:46:39 +0700 Subject: [PATCH 06/17] Ignoring dev-specific test again. --- .../bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java index 2a07b91b8e..95d648bc89 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java @@ -17,11 +17,12 @@ import org.bouncycastle.openpgp.wot.key.PgpKey; import org.bouncycastle.openpgp.wot.key.PgpKeyId; import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//@Ignore("This test is for playing around while developing - it's not a regular test!") +@Ignore("This test is for playing around while developing - it's not a regular test!") public class TrustDbProductiveFileTest extends AbstractTrustDbTest { private static final Logger logger = LoggerFactory.getLogger(TrustDbProductiveFileTest.class); From fa9deba574702a4f9f984ad549db51f5a88fd95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 17:02:34 +0700 Subject: [PATCH 07/17] Moved @SuppressWarnings("unchecked") into method. --- .../openpgp/wot/key/PgpKeyRegistry.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index 15a3a09a86..12cf62b581 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -436,7 +436,6 @@ public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) return Collections.unmodifiableSet(pgpKeyIds); } - @SuppressWarnings("unchecked") protected synchronized Map> getSigningKeyId2signedKeyIds() { loadIfNeeded(); @@ -450,34 +449,34 @@ protected synchronized Map> getSigningKeyId2signedKeyIds { if (pgpUserId.getUserId() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it - .hasNext();) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (isCertification(pgpSignature)) enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); } - } - else if (pgpUserId.getUserAttribute() != null) + } else if (pgpUserId.getUserAttribute() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId - .getUserAttribute())); it.hasNext();) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (isCertification(pgpSignature)) enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); } - } - else + } else throw new IllegalStateException("WTF?!"); } - // It seems, there are both: certifications for individual user-ids and certifications for the - // entire key. I therefore first take the individual ones (above) into account then and then + // It seems, there are both: certifications for individual + // user-ids and certifications for the + // entire key. I therefore first take the individual ones + // (above) into account then and then // the ones for the entire key (below). - // Normally, the signatures bound to the key are never 'certifications', but it rarely happens. - // Don't know, if these are malformed or deprecated (very old) keys, but I should take them into account. - for (Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) + // Normally, the signatures bound to the key are never + // 'certifications', but it rarely happens. + // Don't know, if these are malformed or deprecated (very old) + // keys, but I should take them into account. + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (isCertification(pgpSignature)) From 1f14a1e6ae7bf30e18648cdc5e187a5aad3cd865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 17:04:59 +0700 Subject: [PATCH 08/17] Again moved @SuppressWarnings("unchecked") into method. --- .../org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index 12cf62b581..e1d6f38e04 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -496,7 +496,6 @@ protected synchronized Map> getSigningKeyId2signedKeyIds * @return the certifications authenticating the given {@code pgpUserId}. Never null. Because every * user-ID is normally at least signed by the owning key, it is normally never empty, too. */ - @SuppressWarnings("unchecked") public synchronized List getSignatures(final PgpUserId pgpUserId) { assertNotNull("pgpUserId", pgpUserId); @@ -507,7 +506,7 @@ public synchronized List getSignatures(final PgpUserId pgpUserId) final List result = new ArrayList<>(); if (pgpUserId.getUserId() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) @@ -519,8 +518,7 @@ public synchronized List getSignatures(final PgpUserId pgpUserId) } else if (pgpUserId.getUserAttribute() != null) { - for (final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId - .getUserAttribute())); it.hasNext();) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) @@ -535,7 +533,7 @@ else if (pgpUserId.getUserAttribute() != null) // There are also key-signatures which are not for a certain indivdual user-id/-attribute, but for the entire key. // See the comment in getSigningKeyId2signedKeyIds() above for more details. - for (final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) From 8977ff7b4a6197bbaa2749ee61208435d9a93177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 18:12:16 +0700 Subject: [PATCH 09/17] Extracted interfaces TrustDb and PgpKeyRegistry. --- .../bouncycastle/openpgp/wot/OwnerTrust.java | 2 +- .../org/bouncycastle/openpgp/wot/TrustDb.java | 769 +---------------- .../openpgp/wot/TrustDbException.java | 2 +- .../bouncycastle/openpgp/wot/TrustDbImpl.java | 807 ++++++++++++++++++ .../bouncycastle/openpgp/wot/Validity.java | 4 +- .../openpgp/wot/internal/TrustDbIo.java | 4 +- .../openpgp/wot/key/PgpKeyRegistry.java | 463 +--------- .../openpgp/wot/key/PgpKeyRegistryImpl.java | 500 +++++++++++ .../openpgp/wot/key/package-info.java | 2 +- .../openpgp/wot/package-info.java | 2 +- .../openpgp/wot/AbstractTrustDbTest.java | 3 +- .../wot/TrustDbProductiveFileTest.java | 6 +- .../openpgp/wot/UpdateTrustDbTest.java | 40 +- 13 files changed, 1381 insertions(+), 1223 deletions(-) create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java index 56a9ef31d4..f3cbe998ff 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java @@ -19,7 +19,7 @@ * to her key. * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co - * @see TrustDb#getOwnerTrust(org.bouncycastle.openpgp.wot.key.PgpKey) + * @see TrustDbImpl#getOwnerTrust(org.bouncycastle.openpgp.wot.key.PgpKey) */ public enum OwnerTrust { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java index d3be2b3976..93dda6e408 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java @@ -1,39 +1,14 @@ package org.bouncycastle.openpgp.wot; -import static org.bouncycastle.openpgp.wot.internal.Util.*; - -import java.io.File; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.wot.internal.PgpKeyTrust; -import org.bouncycastle.openpgp.wot.internal.PgpUserIdTrust; -import org.bouncycastle.openpgp.wot.internal.TrustDbIo; -import org.bouncycastle.openpgp.wot.internal.TrustRecord; -import org.bouncycastle.openpgp.wot.internal.TrustRecordType; import org.bouncycastle.openpgp.wot.key.PgpKey; -import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; -import org.bouncycastle.openpgp.wot.key.PgpKeyId; -import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; import org.bouncycastle.openpgp.wot.key.PgpUserId; import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * API for working with GnuPG's {@code trustdb.gpg}. *

- * An instance of this class is used for the following purposes: + * An instance is used for the following purposes: *

    *
  • Read the validity of a {@linkplain #getValidityRaw(PGPPublicKey) certain key}, * {@linkplain #getValidityRaw(PGPPublicKey, PgpUserIdNameHash) user-identity or user-attribute}. @@ -42,105 +17,13 @@ *
  • Set a key's {@linkplain #setOwnerTrust(PGPPublicKey, int) owner-trust} attribute. *
  • {@linkplain #updateTrustDb() Recalculate the web-of-trust}. *
- *

- * This class was mostly ported from the GnuPG's {@code trustdb.h} and {@code trustdb.c} files. * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class TrustDb implements AutoCloseable, TrustConst +public interface TrustDb extends AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(TrustDb.class); - - private final TrustDbIo trustDbIo; - private final PgpKeyRegistry pgpKeyRegistry; - - private long startTime; - private long nextExpire; - private Map fingerprint2PgpKeyTrust; - private Set klist; - private Set fullTrust; - private DateFormat dateFormatIso8601WithTime; - - /** - * Create a {@code TrustDb} instance with the given {@code trustdb.gpg} file and the given key-registry. - *

- * Important: You must {@linkplain #close() close} this instance! - * - * @param file - * the trust-database-file ({@code trustdb.gpg}). Must not be null. - * @param pgpKeyRegistry - * the key-registry. Must not be null. - */ - public TrustDb(final File file, final PgpKeyRegistry pgpKeyRegistry) - { - assertNotNull("file", file); - this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); - this.trustDbIo = new TrustDbIo(file); - } - @Override - public void close() throws Exception - { - trustDbIo.close(); - } - - public DateFormat getDateFormatIso8601WithTime() - { - if (dateFormatIso8601WithTime == null) - dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - - return dateFormatIso8601WithTime; - } - - protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) - { - PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); - if (pgpKeyTrust == null) - { - pgpKeyTrust = new PgpKeyTrust(pgpKey); - fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); - } - return pgpKeyTrust; - } - - // reset_trust_records(void) - protected void resetTrustRecords() - { - TrustRecord record; - long recordNum = 0; - int count = 0, nreset = 0; - - while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) - { - if (record.getType() == TrustRecordType.TRUST) - { - final TrustRecord.Trust trust = (TrustRecord.Trust) record; - ++count; - if (trust.getMinOwnerTrust() != 0) - { - trust.setMinOwnerTrust((short) 0); - trustDbIo.putTrustRecord(record); - } - } - else if (record.getType() == TrustRecordType.VALID) - { - final TrustRecord.Valid valid = (TrustRecord.Valid) record; - if (((valid.getValidity() & TRUST_MASK) != 0) - || valid.getMarginalCount() != 0 - || valid.getFullCount() != 0) - { - - valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); - valid.setMarginalCount((short) 0); - valid.setFullCount((short) 0); - nreset++; - trustDbIo.putTrustRecord(record); - } - } - } - - logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); - } + void close(); /** * Gets the assigned owner-trust value for the given public key. @@ -155,14 +38,7 @@ else if (record.getType() == TrustRecordType.VALID) * @see #setOwnerTrust(PgpKey, OwnerTrust) * @see #getOwnerTrust(PGPPublicKey) */ - public OwnerTrust getOwnerTrust(PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - return getOwnerTrust(pgpKey.getPublicKey()); - } + OwnerTrust getOwnerTrust(PgpKey pgpKey); /** * Sets the given key's owner-trust. @@ -179,15 +55,7 @@ public OwnerTrust getOwnerTrust(PgpKey pgpKey) * @see #getOwnerTrust(PgpKey) * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) */ - public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) - { - assertNotNull("pgpKey", pgpKey); - assertNotNull("ownerTrust", ownerTrust); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); - } + void setOwnerTrust(PgpKey pgpKey, OwnerTrust ownerTrust); /** * Gets the assigned owner-trust value for the given public key. @@ -204,18 +72,7 @@ public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) * @see #getOwnerTrust(PgpKey) */ - public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - // if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) - // return TRUST_UNKNOWN; // TODO maybe we should support other trust models... - - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - return OwnerTrust.UNKNOWN; - - return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); - } + OwnerTrust getOwnerTrust(PGPPublicKey publicKey); /** * Sets the given key's owner-trust. @@ -234,34 +91,7 @@ public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) * @see #getOwnerTrust(PGPPublicKey) * @see #setOwnerTrust(PgpKey, OwnerTrust) */ - public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) - { - assertNotNull("publicKey", publicKey); - assertNotNull("ownerTrust", ownerTrust); - - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - { - // No record yet - create a new one. - trust = new TrustRecord.Trust(); - trust.setFingerprint(publicKey.getFingerprint()); - } - - int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; - - trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); - trustDbIo.putTrustRecord(trust); - - markTrustDbStale(); - trustDbIo.flush(); - } - - protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); - return trust; - } + void setOwnerTrust(PGPPublicKey publicKey, OwnerTrust ownerTrust); /** * Gets the validity of the given key. @@ -287,11 +117,7 @@ protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) * {@link #getValidity(PGPPublicKey)} instead. */ @Deprecated - public synchronized int getValidityRaw(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - return _getValidity(publicKey, (PgpUserIdNameHash) null, true); - } + int getValidityRaw(PGPPublicKey publicKey); /** * Gets the validity of the given user-identity. @@ -315,12 +141,7 @@ public synchronized int getValidityRaw(final PGPPublicKey publicKey) * {@link #getValidity(PGPPublicKey, PgpUserIdNameHash)} instead. */ @Deprecated - public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) - { - assertNotNull("publicKey", publicKey); - assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); - return _getValidity(publicKey, pgpUserIdNameHash, true); - } + int getValidityRaw(PGPPublicKey publicKey, PgpUserIdNameHash pgpUserIdNameHash); /** * Gets the validity of the given key. @@ -336,11 +157,7 @@ public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUs * @see #getValidity(PgpKey, PgpUserIdNameHash) * @see #getValidity(PGPPublicKey) */ - public Validity getValidity(final PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - return getValidity(pgpKey.getPublicKey()); - } + Validity getValidity(PgpKey pgpKey); /** * Gets the validity of the given user-identity (or -attribute). @@ -354,11 +171,7 @@ public Validity getValidity(final PgpKey pgpKey) * @see #getValidity(PgpKey) * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) */ - public Validity getValidity(final PgpUserId pgpUserId) - { - assertNotNull("pgpUserId", pgpUserId); - return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); - } + Validity getValidity(PgpUserId pgpUserId); /** * Gets the validity of the given key. @@ -373,12 +186,7 @@ public Validity getValidity(final PgpUserId pgpUserId) * @return the validity of the given {@code publicKey}. Never null. * @see #getValidity(PGPPublicKey, PgpUserIdNameHash) */ - public Validity getValidity(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); - return Validity.fromNumericValue(numericValue); - } + Validity getValidity(PGPPublicKey publicKey); /** * Gets the validity of the given user-identity (or -attribute). @@ -394,134 +202,7 @@ public Validity getValidity(final PGPPublicKey publicKey) * @return the validity of the given user-identity. Never null. * @see #getValidity(PGPPublicKey) */ - public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) - { - assertNotNull("publicKey", publicKey); - assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); - final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); - return Validity.fromNumericValue(numericValue); - } - - /** - * Ported from - * {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} - */ - protected synchronized int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, - final boolean withFlags) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - return TRUST_UNKNOWN; - - // Loop over all user IDs - long recordNum = trust.getValidList(); - int validity = 0; - int flags = 0; - // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust - // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, - // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). - while (recordNum != 0) - { - TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); - assertNotNull("valid", valid); - - if (pgpUserIdNameHash != null) - { - // If a user ID is given we return the validity for that - // user ID ONLY. If the namehash is not found, then there - // is no validity at all (i.e. the user ID wasn't signed). - if (pgpUserIdNameHash.equals(valid.getNameHash())) - { - validity = valid.getValidity() & TRUST_MASK; - flags = valid.getValidity() & ~TRUST_MASK; - break; - } - } - else - { - // If no user ID is given, we take the maximum validity over all user IDs - validity = Math.max(validity, valid.getValidity() & TRUST_MASK); - flags |= valid.getValidity() & ~TRUST_MASK; - } - recordNum = valid.getNext(); - } - - if (withFlags) - { - validity |= flags; - - if ((trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0) - validity |= TRUST_FLAG_DISABLED; - - if (publicKey.hasRevocation()) - validity |= TRUST_FLAG_REVOKED; - - if (isTrustDbStale()) - validity |= TRUST_FLAG_PENDING_CHECK; - } - return validity; - } - - // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) - protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) - { - assertNotNull("pgpUserId", pgpUserId); - assertNonNegativeShort("depth", depth); - assertNonNegativeShort("validity", validity); - assertNonNegativeShort("fullCount", fullCount); - assertNonNegativeShort("marginalCount", marginalCount); - - TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); - if (trust == null) - { - // No record yet - create a new one. - trust = new TrustRecord.Trust(); - trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); - trustDbIo.putTrustRecord(trust); - } - - TrustRecord.Valid valid = null; - - // locate an existing Valid record - final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); - long recordNum = trust.getValidList(); - while (recordNum != 0) - { - valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); - if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) - break; - - recordNum = valid.getNext(); - } - - if (recordNum == 0) - { // insert a new validity record - valid = new TrustRecord.Valid(); - valid.setNameHash(pgpUserIdNameHashBytes); - valid.setNext(trust.getValidList()); - trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record - trust.setValidList(valid.getRecordNum()); - } - - valid.setValidity((short) validity); - valid.setFullCount((short) fullCount); - valid.setMarginalCount((short) marginalCount); - trust.setDepth((short) depth); - trustDbIo.putTrustRecord(trust); - trustDbIo.putTrustRecord(valid); - } - - private static void assertNonNegativeShort(final String name, final int value) - { - assertNotNull("name", name); - - if (value < 0) - throw new IllegalArgumentException(name + " < 0"); - - if (value > Short.MAX_VALUE) - throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); - } + Validity getValidity(PGPPublicKey publicKey, PgpUserIdNameHash pgpUserIdNameHash); /** * Marks all those keys that we have a secret key for as ultimately trusted. If we have a secret/private key, we @@ -530,67 +211,9 @@ private static void assertNonNegativeShort(final String name, final int value) * @param onlyIfMissing * whether only those keys' owner-trust should be set which do not yet have an owner-trust assigned. */ - public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) - { - for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) - { - if (masterKey.getSecretKey() == null) - continue; - - TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); - if (trust == null - || trust.getOwnerTrust() == TRUST_UNKNOWN - || !onlyIfMissing) - { - - if (trust == null) - { - trust = new TrustRecord.Trust(); - trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); - } - - trust.setDepth((short) 0); - trust.setOwnerTrust((short) TRUST_ULTIMATE); - trustDbIo.putTrustRecord(trust); - } - } - } - - protected Set getUltimatelyTrustedKeyFingerprints() - { - Set result = new HashSet(); - TrustRecord record; - long recordNum = 0; - while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) - { - if (record.getType() == TrustRecordType.TRUST) - { - TrustRecord.Trust trust = (TrustRecord.Trust) record; - if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) - result.add(new PgpKeyFingerprint(trust.getFingerprint())); - } - } - return result; - } - - public boolean isExpired(PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); + void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing); - final Date creationTime = publicKey.getCreationTime(); - - final long validSeconds = publicKey.getValidSeconds(); - if (validSeconds != 0) - { - long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); - return validUntilTimestamp < System.currentTimeMillis(); - } - return false; - // TODO there seem to be keys (very old keys) that seem to encode the validity differently. - // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it - // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. - // It's a very small number of keys only, hence I ignore it for now ;-) - } + boolean isExpired(PGPPublicKey publicKey); /** * Determines whether the specified key is marked as disabled. @@ -599,14 +222,7 @@ public boolean isExpired(PGPPublicKey publicKey) * the key whose status to query. Must not be null. * @return true, if the key is marked as disabled; false, if the key is enabled. */ - public boolean isDisabled(PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - return isDisabled(pgpKey.getPublicKey()); - } + boolean isDisabled(PgpKey pgpKey); /** * Enables or disabled the specified key. @@ -616,14 +232,7 @@ public boolean isDisabled(PgpKey pgpKey) * @param disabled * true to disable the key; false to enable it. */ - public void setDisabled(PgpKey pgpKey, final boolean disabled) - { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - setDisabled(pgpKey.getPublicKey(), disabled); - } + void setDisabled(PgpKey pgpKey, boolean disabled); /** * Determines whether the specified key is marked as disabled. @@ -634,15 +243,7 @@ public void setDisabled(PgpKey pgpKey, final boolean disabled) * the key whose status to query. Must not be null. This should be a master-key. * @return true, if the key is marked as disabled; false, if the key is enabled. */ - public boolean isDisabled(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); - if (trust == null) - return false; - - return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; - } + boolean isDisabled(PGPPublicKey publicKey); /** * Enables or disabled the specified key. @@ -654,27 +255,7 @@ public boolean isDisabled(final PGPPublicKey publicKey) * @param disabled * true to disable the key; false to enable it. */ - public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); - if (trust == null) - { - trust = new TrustRecord.Trust(); - trust.setFingerprint(publicKey.getFingerprint()); - } - - int ownerTrust = trust.getOwnerTrust(); - if (disabled) - ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; - else - ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; - - trust.setOwnerTrust((short) ownerTrust); - - trustDbIo.putTrustRecord(trust); - trustDbIo.flush(); - } + void setDisabled(PGPPublicKey publicKey, boolean disabled); /** * Determines if the trust-database is stale. It becomes stale, if it is either explicitly @@ -688,75 +269,7 @@ public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) * @see #updateTrustDb() * @see #updateTrustDbIfNeeded() */ - public synchronized boolean isTrustDbStale() - { - final Config config = Config.getInstance(); - final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - if (config.getTrustModel() != version.getTrustModel()) - { - TrustModel configTrustModel; - try - { - configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); - } catch (IllegalArgumentException x) - { - configTrustModel = null; - } - - TrustModel versionTrustModel; - try - { - versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); - } catch (IllegalArgumentException x) - { - versionTrustModel = null; - } - - logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", - config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); - - return true; - } - - if (config.getCompletesNeeded() != version.getCompletesNeeded()) - { - logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", - config.getCompletesNeeded(), version.getCompletesNeeded()); - - return true; - } - - if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) - { - logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", - config.getMarginalsNeeded(), version.getMarginalsNeeded()); - - return true; - } - - if (config.getMaxCertDepth() != version.getCertDepth()) - { - logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", - config.getMaxCertDepth(), version.getCertDepth()); - - return true; - } - - final Date now = new Date(); - if (version.getNextCheck().before(now)) - { - logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", - getDateFormatIso8601WithTime().format(version.getNextCheck()), - getDateFormatIso8601WithTime().format(now)); - - return true; - } - - logger.trace("isTrustDbStale: stale=false"); - return false; - } + boolean isTrustDbStale(); /** * Marks the trust-db as being stale. @@ -768,13 +281,7 @@ public synchronized boolean isTrustDbStale() * @see #isTrustDbStale() * @see #updateTrustDb() */ - public synchronized void markTrustDbStale() - { - final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - version.setNextCheck(new Date(0)); - trustDbIo.putTrustRecord(version); - } + void markTrustDbStale(); /** * Update the {@code trustdb.gpg} by recalculating all keys' validities, if it is needed. An update is needed, if @@ -783,11 +290,7 @@ public synchronized void markTrustDbStale() * @see #updateTrustDb() * @see #isTrustDbStale() */ - public synchronized void updateTrustDbIfNeeded() - { - if (isTrustDbStale()) - updateTrustDb(); - } + void updateTrustDbIfNeeded(); /** * Update the {@code trustdb.gpg} by recalculating all keys' validities. @@ -795,233 +298,9 @@ public synchronized void updateTrustDbIfNeeded() * Either this method or {@link #markTrustDbStale()} must be invoked whenever a new key was added to the key ring, * because the WOT-related code does not keep track of key-ring-changes ({@link #isTrustDbStale()} does not detect * them). - *

- * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, because the - * implementation looked overly complicated. This method here is a re-implementation from scratch. It still seems to - * come very closely to the behaviour of GnuPG's original code. * * @see #updateTrustDbIfNeeded() */ - public synchronized void updateTrustDb() - { - final Config config = Config.getInstance(); - try - { - fingerprint2PgpKeyTrust = new HashMap<>(); - fullTrust = new HashSet<>(); - - startTime = System.currentTimeMillis() / 1000; - nextExpire = Long.MAX_VALUE; - - resetTrustRecords(); - - final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); - if (ultimatelyTrustedKeyFingerprints.isEmpty()) - { - logger.warn("updateTrustDb: There are no ultimately trusted keys!"); - return; - } - - // mark all UTKs as used and fully_trusted and set validity to ultimate - for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) - { - final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); - if (utk == null) - { - logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); - continue; - } - - fullTrust.add(utkFpr); - - for (PgpUserId pgpUserId : utk.getPgpUserIds()) - updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); - - final long expireDate = getExpireTimestamp(utk.getPublicKey()); - if (expireDate >= startTime && expireDate < nextExpire) - nextExpire = expireDate; - } - - klist = ultimatelyTrustedKeyFingerprints; - - for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) - { - final List validatedKeys = validateKeyList(); - - klist = new HashSet<>(); - for (PgpKey pgpKey : validatedKeys) - { - PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); - klist.add(pgpKey.getPgpKeyFingerprint()); - - for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) - { - final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); - - final int validity = pgpUserIdTrust.getValidity(); - updateValidity(pgpUserId, depth, validity, - pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); - - if (validity >= TRUST_FULLY) - fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); - } - - final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); - if (expireDate >= startTime && expireDate < nextExpire) - nextExpire = expireDate; - } - - logger.debug("updateTrustDb: depth={} keys={}", - depth, validatedKeys.size()); - } - - final Date nextExpireDate = new Date(nextExpire * 1000); - trustDbIo.updateVersionRecord(nextExpireDate); - - trustDbIo.flush(); - - logger.info("updateTrustDb: Next trust-db expiration date: {}", - getDateFormatIso8601WithTime().format(nextExpireDate)); - } finally - { - fingerprint2PgpKeyTrust = null; - klist = null; - fullTrust = null; - } - } - - private long getExpireTimestamp(PGPPublicKey pk) - { - final long validSeconds = pk.getValidSeconds(); - if (validSeconds == 0) - return Long.MAX_VALUE; - - final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; - return result; - } - - /** - * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, - * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see - * {@link #updateTrustDb()}. - * - * @return the keys that were processed by this method. - */ - private List validateKeyList() - { - final List result = new ArrayList<>(); - final Set signedPgpKeyFingerprints = new HashSet<>(); - for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) - signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); - - signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted - - for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) - { - final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); - if (pgpKey == null) - { - logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); - continue; - } - result.add(pgpKey); - validateKey(pgpKey); - } - return result; - } - - /** - * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, - * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. - * - * @param pgpKey - * the pgp-key to be validated. Must not be null. - */ - private void validateKey(final PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - logger.debug("validateKey: {}", pgpKey); - - final Config config = Config.getInstance(); - final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); - - final boolean expired = isExpired(pgpKey.getPublicKey()); - // final boolean disabled = isDisabled(pgpKey.getPublicKey()); - final boolean revoked = pgpKey.getPublicKey().hasRevocation(); - - for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) - { - final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); - - pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 - pgpUserIdTrust.setUltimateCount(0); - pgpUserIdTrust.setFullCount(0); - pgpUserIdTrust.setMarginalCount(0); - - if (expired) - continue; - - // if (disabled) - // continue; - - if (revoked) - continue; - - for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) - { - // It seems, the PGP trust model does not care about the certification level :-( - // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - - // there is no difference (at least according to my tests). - if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION - && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION - && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) - continue; - - final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); - if (signingKey == null) - continue; - - final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); - if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) - && signingOwnerTrust != OwnerTrust.ULTIMATE) - { - // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. - continue; - } - - int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; - if (signingValidity <= TRUST_MARGINAL) - { - // If the signingKey is trusted only marginally or less, we ignore the certification completely. - // Only fully trusted keys are taken into account for transitive trust. - continue; - } - - // The owner-trust of the signing key is relevant. - switch (signingOwnerTrust) - { - case ULTIMATE: - pgpUserIdTrust.incUltimateCount(); - break; - case FULLY: - pgpUserIdTrust.incFullCount(); - break; - case MARGINAL: - pgpUserIdTrust.incMarginalCount(); - break; - default: // ignoring! - break; - } - } + void updateTrustDb(); - if (pgpUserIdTrust.getUltimateCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_FULLY); - else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULLY); - else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULLY); - else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_MARGINAL); - } - } -} +} \ No newline at end of file diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java index 49b58290a6..e13434a48c 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java @@ -1,7 +1,7 @@ package org.bouncycastle.openpgp.wot; /** - * Exception thrown by {@link TrustDb} and related classes. + * Exception thrown by {@link TrustDbImpl} and related classes. * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java new file mode 100644 index 0000000000..8699ce8c34 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java @@ -0,0 +1,807 @@ +package org.bouncycastle.openpgp.wot; + +import static org.bouncycastle.openpgp.wot.internal.Util.*; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.wot.internal.PgpKeyTrust; +import org.bouncycastle.openpgp.wot.internal.PgpUserIdTrust; +import org.bouncycastle.openpgp.wot.internal.TrustDbIo; +import org.bouncycastle.openpgp.wot.internal.TrustRecord; +import org.bouncycastle.openpgp.wot.internal.TrustRecordType; +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; +import org.bouncycastle.openpgp.wot.key.PgpKeyId; +import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; +import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * API implementation for working with GnuPG's {@code trustdb.gpg}. + *

+ * This class was mostly ported from the GnuPG's {@code trustdb.h} and {@code trustdb.c} files. + * + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ +public class TrustDbImpl implements AutoCloseable, TrustConst, TrustDb +{ + private static final Logger logger = LoggerFactory.getLogger(TrustDbImpl.class); + + private final TrustDbIo trustDbIo; + private final PgpKeyRegistry pgpKeyRegistry; + + private long startTime; + private long nextExpire; + private Map fingerprint2PgpKeyTrust; + private Set klist; + private Set fullTrust; + private DateFormat dateFormatIso8601WithTime; + + /** + * Create a {@code TrustDbImpl} instance with the given {@code trustdb.gpg} file and the given key-registry. + *

+ * Important: You must {@linkplain #close() close} this instance! + * + * @param file + * the trust-database-file ({@code trustdb.gpg}). Must not be null. + * @param pgpKeyRegistry + * the key-registry. Must not be null. + */ + public TrustDbImpl(final File file, final PgpKeyRegistry pgpKeyRegistry) + { + assertNotNull("file", file); + this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); + this.trustDbIo = new TrustDbIo(file); + } + + @Override + public void close() + { + trustDbIo.close(); + } + + public DateFormat getDateFormatIso8601WithTime() + { + if (dateFormatIso8601WithTime == null) + dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + return dateFormatIso8601WithTime; + } + + protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) + { + PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); + if (pgpKeyTrust == null) + { + pgpKeyTrust = new PgpKeyTrust(pgpKey); + fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); + } + return pgpKeyTrust; + } + + // reset_trust_records(void) + protected void resetTrustRecords() + { + TrustRecord record; + long recordNum = 0; + int count = 0, nreset = 0; + + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) + { + if (record.getType() == TrustRecordType.TRUST) + { + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + ++count; + if (trust.getMinOwnerTrust() != 0) + { + trust.setMinOwnerTrust((short) 0); + trustDbIo.putTrustRecord(record); + } + } + else if (record.getType() == TrustRecordType.VALID) + { + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + if (((valid.getValidity() & TRUST_MASK) != 0) + || valid.getMarginalCount() != 0 + || valid.getFullCount() != 0) + { + + valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); + valid.setMarginalCount((short) 0); + valid.setFullCount((short) 0); + nreset++; + trustDbIo.putTrustRecord(record); + } + } + } + + logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); + } + + @Override + public OwnerTrust getOwnerTrust(PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return getOwnerTrust(pgpKey.getPublicKey()); + } + + @Override + public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) + { + assertNotNull("pgpKey", pgpKey); + assertNotNull("ownerTrust", ownerTrust); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); + } + + @Override + public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + // if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + // return TRUST_UNKNOWN; // TODO maybe we should support other trust models... + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return OwnerTrust.UNKNOWN; + + return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); + } + + @Override + public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) + { + assertNotNull("publicKey", publicKey); + assertNotNull("ownerTrust", ownerTrust); + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; + + trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); + trustDbIo.putTrustRecord(trust); + + markTrustDbStale(); + trustDbIo.flush(); + } + + protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); + return trust; + } + + @Override + @Deprecated + public synchronized int getValidityRaw(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + return _getValidity(publicKey, (PgpUserIdNameHash) null, true); + } + + @Override + @Deprecated + public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) + { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + return _getValidity(publicKey, pgpUserIdNameHash, true); + } + + @Override + public Validity getValidity(final PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + return getValidity(pgpKey.getPublicKey()); + } + + @Override + public Validity getValidity(final PgpUserId pgpUserId) + { + assertNotNull("pgpUserId", pgpUserId); + return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); + } + + @Override + public Validity getValidity(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); + return Validity.fromNumericValue(numericValue); + } + + @Override + public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) + { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); + return Validity.fromNumericValue(numericValue); + } + + /** + * Ported from + * {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} + */ + protected synchronized int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, + final boolean withFlags) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return TRUST_UNKNOWN; + + // Loop over all user IDs + long recordNum = trust.getValidList(); + int validity = 0; + int flags = 0; + // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust + // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, + // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). + while (recordNum != 0) + { + TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + assertNotNull("valid", valid); + + if (pgpUserIdNameHash != null) + { + // If a user ID is given we return the validity for that + // user ID ONLY. If the namehash is not found, then there + // is no validity at all (i.e. the user ID wasn't signed). + if (pgpUserIdNameHash.equals(valid.getNameHash())) + { + validity = valid.getValidity() & TRUST_MASK; + flags = valid.getValidity() & ~TRUST_MASK; + break; + } + } + else + { + // If no user ID is given, we take the maximum validity over all user IDs + validity = Math.max(validity, valid.getValidity() & TRUST_MASK); + flags |= valid.getValidity() & ~TRUST_MASK; + } + recordNum = valid.getNext(); + } + + if (withFlags) + { + validity |= flags; + + if ((trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0) + validity |= TRUST_FLAG_DISABLED; + + if (publicKey.hasRevocation()) + validity |= TRUST_FLAG_REVOKED; + + if (isTrustDbStale()) + validity |= TRUST_FLAG_PENDING_CHECK; + } + return validity; + } + + // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) + protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) + { + assertNotNull("pgpUserId", pgpUserId); + assertNonNegativeShort("depth", depth); + assertNonNegativeShort("validity", validity); + assertNonNegativeShort("fullCount", fullCount); + assertNonNegativeShort("marginalCount", marginalCount); + + TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); + if (trust == null) + { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); + trustDbIo.putTrustRecord(trust); + } + + TrustRecord.Valid valid = null; + + // locate an existing Valid record + final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); + long recordNum = trust.getValidList(); + while (recordNum != 0) + { + valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) + break; + + recordNum = valid.getNext(); + } + + if (recordNum == 0) + { // insert a new validity record + valid = new TrustRecord.Valid(); + valid.setNameHash(pgpUserIdNameHashBytes); + valid.setNext(trust.getValidList()); + trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record + trust.setValidList(valid.getRecordNum()); + } + + valid.setValidity((short) validity); + valid.setFullCount((short) fullCount); + valid.setMarginalCount((short) marginalCount); + trust.setDepth((short) depth); + trustDbIo.putTrustRecord(trust); + trustDbIo.putTrustRecord(valid); + } + + private static void assertNonNegativeShort(final String name, final int value) + { + assertNotNull("name", name); + + if (value < 0) + throw new IllegalArgumentException(name + " < 0"); + + if (value > Short.MAX_VALUE) + throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); + } + + /* (non-Javadoc) + * @see org.bouncycastle.openpgp.wot.TrustDb#updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean) + */ + @Override + public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) + { + for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) + { + if (masterKey.getSecretKey() == null) + continue; + + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); + if (trust == null + || trust.getOwnerTrust() == TRUST_UNKNOWN + || !onlyIfMissing) + { + + if (trust == null) + { + trust = new TrustRecord.Trust(); + trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); + } + + trust.setDepth((short) 0); + trust.setOwnerTrust((short) TRUST_ULTIMATE); + trustDbIo.putTrustRecord(trust); + } + } + } + + protected Set getUltimatelyTrustedKeyFingerprints() + { + Set result = new HashSet(); + TrustRecord record; + long recordNum = 0; + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) + { + if (record.getType() == TrustRecordType.TRUST) + { + TrustRecord.Trust trust = (TrustRecord.Trust) record; + if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) + result.add(new PgpKeyFingerprint(trust.getFingerprint())); + } + } + return result; + } + + @Override + public boolean isExpired(PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + + final Date creationTime = publicKey.getCreationTime(); + + final long validSeconds = publicKey.getValidSeconds(); + if (validSeconds != 0) + { + long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); + return validUntilTimestamp < System.currentTimeMillis(); + } + return false; + // TODO there seem to be keys (very old keys) that seem to encode the validity differently. + // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it + // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. + // It's a very small number of keys only, hence I ignore it for now ;-) + } + + @Override + public boolean isDisabled(PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return isDisabled(pgpKey.getPublicKey()); + } + + @Override + public void setDisabled(PgpKey pgpKey, final boolean disabled) + { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setDisabled(pgpKey.getPublicKey(), disabled); + } + + @Override + public boolean isDisabled(final PGPPublicKey publicKey) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + return false; + + return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; + } + + @Override + public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) + { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + { + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrust = trust.getOwnerTrust(); + if (disabled) + ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; + else + ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; + + trust.setOwnerTrust((short) ownerTrust); + + trustDbIo.putTrustRecord(trust); + trustDbIo.flush(); + } + + @Override + public synchronized boolean isTrustDbStale() + { + final Config config = Config.getInstance(); + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + if (config.getTrustModel() != version.getTrustModel()) + { + TrustModel configTrustModel; + try + { + configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); + } catch (IllegalArgumentException x) + { + configTrustModel = null; + } + + TrustModel versionTrustModel; + try + { + versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); + } catch (IllegalArgumentException x) + { + versionTrustModel = null; + } + + logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", + config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); + + return true; + } + + if (config.getCompletesNeeded() != version.getCompletesNeeded()) + { + logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", + config.getCompletesNeeded(), version.getCompletesNeeded()); + + return true; + } + + if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) + { + logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", + config.getMarginalsNeeded(), version.getMarginalsNeeded()); + + return true; + } + + if (config.getMaxCertDepth() != version.getCertDepth()) + { + logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", + config.getMaxCertDepth(), version.getCertDepth()); + + return true; + } + + final Date now = new Date(); + if (version.getNextCheck().before(now)) + { + logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", + getDateFormatIso8601WithTime().format(version.getNextCheck()), + getDateFormatIso8601WithTime().format(now)); + + return true; + } + + logger.trace("isTrustDbStale: stale=false"); + return false; + } + + @Override + public synchronized void markTrustDbStale() + { + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + version.setNextCheck(new Date(0)); + trustDbIo.putTrustRecord(version); + } + + @Override + public synchronized void updateTrustDbIfNeeded() + { + if (isTrustDbStale()) + updateTrustDb(); + } + + /** + * {@inheritDoc} + *

+ * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, because the + * implementation looked overly complicated. This method here is a re-implementation from scratch. It still seems to + * come very closely to the behaviour of GnuPG's original code. + */ + @Override + public synchronized void updateTrustDb() + { + final Config config = Config.getInstance(); + try + { + fingerprint2PgpKeyTrust = new HashMap<>(); + fullTrust = new HashSet<>(); + + startTime = System.currentTimeMillis() / 1000; + nextExpire = Long.MAX_VALUE; + + resetTrustRecords(); + + final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); + if (ultimatelyTrustedKeyFingerprints.isEmpty()) + { + logger.warn("updateTrustDb: There are no ultimately trusted keys!"); + return; + } + + // mark all UTKs as used and fully_trusted and set validity to ultimate + for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) + { + final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); + if (utk == null) + { + logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); + continue; + } + + fullTrust.add(utkFpr); + + for (PgpUserId pgpUserId : utk.getPgpUserIds()) + updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); + + final long expireDate = getExpireTimestamp(utk.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + klist = ultimatelyTrustedKeyFingerprints; + + for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) + { + final List validatedKeys = validateKeyList(); + + klist = new HashSet<>(); + for (PgpKey pgpKey : validatedKeys) + { + PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + klist.add(pgpKey.getPgpKeyFingerprint()); + + for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) + { + final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); + + final int validity = pgpUserIdTrust.getValidity(); + updateValidity(pgpUserId, depth, validity, + pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); + + if (validity >= TRUST_FULLY) + fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); + } + + final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + logger.debug("updateTrustDb: depth={} keys={}", + depth, validatedKeys.size()); + } + + final Date nextExpireDate = new Date(nextExpire * 1000); + trustDbIo.updateVersionRecord(nextExpireDate); + + trustDbIo.flush(); + + logger.info("updateTrustDb: Next trust-db expiration date: {}", + getDateFormatIso8601WithTime().format(nextExpireDate)); + } finally + { + fingerprint2PgpKeyTrust = null; + klist = null; + fullTrust = null; + } + } + + private long getExpireTimestamp(PGPPublicKey pk) + { + final long validSeconds = pk.getValidSeconds(); + if (validSeconds == 0) + return Long.MAX_VALUE; + + final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; + return result; + } + + /** + * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, + * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see + * {@link #updateTrustDb()}. + * + * @return the keys that were processed by this method. + */ + private List validateKeyList() + { + final List result = new ArrayList<>(); + final Set signedPgpKeyFingerprints = new HashSet<>(); + for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) + signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); + + signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted + + for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) + { + final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + { + logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); + continue; + } + result.add(pgpKey); + validateKey(pgpKey); + } + return result; + } + + /** + * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, + * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. + * + * @param pgpKey + * the pgp-key to be validated. Must not be null. + */ + private void validateKey(final PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + logger.debug("validateKey: {}", pgpKey); + + final Config config = Config.getInstance(); + final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + + final boolean expired = isExpired(pgpKey.getPublicKey()); + // final boolean disabled = isDisabled(pgpKey.getPublicKey()); + final boolean revoked = pgpKey.getPublicKey().hasRevocation(); + + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) + { + final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); + + pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 + pgpUserIdTrust.setUltimateCount(0); + pgpUserIdTrust.setFullCount(0); + pgpUserIdTrust.setMarginalCount(0); + + if (expired) + continue; + + // if (disabled) + // continue; + + if (revoked) + continue; + + for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) + { + // It seems, the PGP trust model does not care about the certification level :-( + // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - + // there is no difference (at least according to my tests). + if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION + && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION + && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) + continue; + + final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); + if (signingKey == null) + continue; + + final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); + if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) + && signingOwnerTrust != OwnerTrust.ULTIMATE) + { + // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. + continue; + } + + int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; + if (signingValidity <= TRUST_MARGINAL) + { + // If the signingKey is trusted only marginally or less, we ignore the certification completely. + // Only fully trusted keys are taken into account for transitive trust. + continue; + } + + // The owner-trust of the signing key is relevant. + switch (signingOwnerTrust) + { + case ULTIMATE: + pgpUserIdTrust.incUltimateCount(); + break; + case FULLY: + pgpUserIdTrust.incFullCount(); + break; + case MARGINAL: + pgpUserIdTrust.incMarginalCount(); + break; + default: // ignoring! + break; + } + } + + if (pgpUserIdTrust.getUltimateCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULLY); + else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_MARGINAL); + } + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java index 8a4fc53d9c..ba70f0623d 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java @@ -6,8 +6,8 @@ /** * Validity of a key or user-identity/-attribute. *

- * The validity is calculated by {@link TrustDb#updateTrustDb()} and can be queried by its - * {@link TrustDb#getValidity(org.bouncycastle.openpgp.wot.key.PgpKey) getValidity(PgpKey)} or another overloaded + * The validity is calculated by {@link TrustDbImpl#updateTrustDb()} and can be queried by its + * {@link TrustDbImpl#getValidity(org.bouncycastle.openpgp.wot.key.PgpKey) getValidity(PgpKey)} or another overloaded * {@code getValidity(...)} method. * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java index 76fed22529..510728ebbb 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java @@ -18,7 +18,7 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.wot.Config; import org.bouncycastle.openpgp.wot.TrustConst; -import org.bouncycastle.openpgp.wot.TrustDb; +import org.bouncycastle.openpgp.wot.TrustDbImpl; import org.bouncycastle.openpgp.wot.TrustDbIoException; import org.bouncycastle.openpgp.wot.internal.TrustRecord.HashLst; import org.slf4j.Logger; @@ -32,7 +32,7 @@ * created implicitly! *

* Important: Do not use this class directly, if you don't have good reasons to! Instead, you should use - * the {@link TrustDb}. + * the {@link TrustDbImpl}. *

* This class was mostly ported from the GnuPG's {@code tdbio.h} and {@code tdbio.c} files. * diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index e1d6f38e04..001ab8684b 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -1,41 +1,16 @@ package org.bouncycastle.openpgp.wot.key; -import static org.bouncycastle.openpgp.wot.internal.Util.*; - -import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Registry providing fast access to the keys of a public and a secret key ring collection. *

- * An {@code PgpKeyRegistry} reads the {@code pubring.gpg} and {@code secring.gpg} (normally located in + * A {@code PgpKeyRegistry} reads the {@code pubring.gpg} and {@code secring.gpg} (normally located in * {@code ~/.gnupg/}) and organizes them in {@link PgpKey} instances. It then provides fast lookup by key-id or * fingerprint via {@link #getPgpKey(PgpKeyId)} or {@link #getPgpKey(PgpKeyFingerprint)}. *

@@ -46,57 +21,21 @@ * * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ -public class PgpKeyRegistry +public interface PgpKeyRegistry { - private static final Logger logger = LoggerFactory.getLogger(PgpKeyRegistry.class); - - private final File pubringFile; - private final File secringFile; - - private long pubringFileLastModified = Long.MIN_VALUE; - private long secringFileLastModified = Long.MIN_VALUE; - - private Map pgpKeyFingerprint2pgpKey; // all keys - private Map pgpKeyId2pgpKey; // all keys - private Map pgpKeyId2masterKey; // only master-keys - - private Map> signingKeyId2signedKeyIds; - - /** - * Creates an instance of {@code PgpKeyRegistry} with the given public and secret key ring collection files. - * - * @param pubringFile - * the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/} - * ). Must not be null. The file does not need to exist, though. - * @param secringFile - * the file containing the secret keys - usually named {@code secring.gpg} (located in {@code ~/.gnupg/} - * ). Must not be null. The file does not need to exist, though. - */ - public PgpKeyRegistry(File pubringFile, File secringFile) - { - this.pubringFile = assertNotNull("pubringFile", pubringFile); - this.secringFile = assertNotNull("secringFile", secringFile); - } - /** * Gets the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/}). * * @return the file containing the public keys. Never null. */ - public File getPubringFile() - { - return pubringFile; - } + File getPubringFile(); /** * Gets the file containing the secret keys - usually named {@code secring.gpg} (located in {@code ~/.gnupg/}). * * @return the file containing the secret keys. Never null. */ - public File getSecringFile() - { - return secringFile; - } + File getSecringFile(); /** * Gets the key with the given ID. If no such key exists, an {@link IllegalArgumentException} is thrown. @@ -109,14 +48,7 @@ public File getSecringFile() * @throws IllegalArgumentException * if the given {@code pgpKeyId} is null or there is no key known with this ID. */ - public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentException - { - final PgpKey pgpKey = getPgpKey(pgpKeyId); - if (pgpKey == null) - throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); - - return pgpKey; - } + PgpKey getPgpKeyOrFail(PgpKeyId pgpKeyId) throws IllegalArgumentException; /** * Gets the key with the given ID. If no such key exists, null is returned. @@ -129,13 +61,7 @@ public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentExc * @throws IllegalArgumentException * if the given {@code pgpKeyId} is null. */ - public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException - { - assertNotNull("pgpKeyId", pgpKeyId); - loadIfNeeded(); - final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); - return pgpKey; - } + PgpKey getPgpKey(PgpKeyId pgpKeyId) throws IllegalArgumentException; /** * Gets the key with the given fingerprint. If no such key exists, an {@link IllegalArgumentException} is thrown. @@ -149,14 +75,7 @@ public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgu * if the given {@code pgpKeyFingerprint} is null or there is no key known with this * fingerprint. */ - public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException - { - final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); - if (pgpKey == null) - throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); - - return pgpKey; - } + PgpKey getPgpKeyOrFail(PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException; /** * Gets the key with the given fingerprint. If no such key exists, null is returned. @@ -169,24 +88,14 @@ public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws * @throws IllegalArgumentException * if the given {@code pgpKeyFingerprint} is null. */ - public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException - { - assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); - loadIfNeeded(); - final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); - return pgpKey; - } + PgpKey getPgpKey(PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException; /** * Gets all master-keys. Their sub-keys are accessible via {@link PgpKey#getSubKeys()}. * * @return all master-keys. Never null. */ - public synchronized Collection getMasterKeys() - { - loadIfNeeded(); - return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); - } + Collection getMasterKeys(); /** * Marks this registry stale - causing it to reload at the next read access. @@ -197,189 +106,7 @@ public synchronized Collection getMasterKeys() * In order to make sure that a key ring file modification reliably causes this registry to reload, this method can * be invoked. */ - public void markStale() - { - pubringFileLastModified = Long.MIN_VALUE; - secringFileLastModified = Long.MIN_VALUE; - } - - /** - * Loads the key ring files, if they were not yet read or if this registry is stale. - */ - protected synchronized void loadIfNeeded() - { - if (pgpKeyId2pgpKey == null - || getPubringFile().lastModified() != pubringFileLastModified - || getSecringFile().lastModified() != secringFileLastModified) - { - logger.debug("loadIfNeeded: invoking load()."); - load(); - } - else - logger.trace("loadIfNeeded: *not* invoking load()."); - } - - /** - * Loads the key ring files. - */ - protected synchronized void load() - { - pgpKeyFingerprint2pgpKey = null; - final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); - final Map pgpKeyId2pgpKey = new HashMap<>(); - final Map pgpKeyId2masterKey = new HashMap<>(); - - final long pubringFileLastModified; - final long secringFileLastModified; - try - { - final File secringFile = getSecringFile(); - logger.debug("load: secringFile='{}'", secringFile); - secringFileLastModified = secringFile.lastModified(); - if (secringFile.isFile()) - { - final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; - try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) - { - pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), - new BcKeyFingerprintCalculator()); - } - for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext();) - { - final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); - PgpKey masterKey = null; - for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) - { - final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); - masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, - pgpKeyId2masterKey, masterKey, keyRing, publicKey); - } - - for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext();) - { - final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); - final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); - final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); - if (pgpKey == null) - throw new IllegalStateException( - "Secret key does not have corresponding public key in secret key ring! pgpKeyId=" - + pgpKeyId); - - pgpKey.setSecretKey(secretKey); - logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); - } - } - } - - final File pubringFile = getPubringFile(); - logger.debug("load: pubringFile='{}'", pubringFile); - pubringFileLastModified = pubringFile.lastModified(); - if (pubringFile.isFile()) - { - final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; - try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) - { - pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), - new BcKeyFingerprintCalculator()); - } - - for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext();) - { - final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); - PgpKey masterKey = null; - for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) - { - final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); - masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, - pgpKeyId2masterKey, masterKey, keyRing, publicKey); - } - } - } - } catch (IOException | PGPException x) - { - throw new RuntimeException(x); - } - - for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) - { - if (pgpKey.getPublicKey() == null) - throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); - - if (pgpKey.getPublicKeyRing() == null) - throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); - } - - this.secringFileLastModified = secringFileLastModified; - this.pubringFileLastModified = pubringFileLastModified; - this.pgpKeyFingerprint2pgpKey = pgpKeyFingerprint2pgpKey; - this.pgpKeyId2pgpKey = pgpKeyId2pgpKey; - this.pgpKeyId2masterKey = pgpKeyId2masterKey; - - assignSubKeys(); - } - - private void assignSubKeys() - { - for (final PgpKey masterKey : pgpKeyId2masterKey.values()) - { - final Set subKeyIds = masterKey.getSubKeyIds(); - final List subKeys = new ArrayList(subKeyIds.size()); - for (final PgpKeyId subKeyId : subKeyIds) - { - final PgpKey subKey = getPgpKeyOrFail(subKeyId); - subKeys.add(subKey); - } - masterKey.setSubKeys(Collections.unmodifiableList(subKeys)); - masterKey.setSubKeyIds(Collections.unmodifiableSet(subKeyIds)); - } - } - - private PgpKey enlistPublicKey(final Map pgpKeyFingerprint2pgpKey, - final Map pgpKeyId2PgpKey, - final Map pgpKeyId2masterKey, - PgpKey masterKey, final PGPKeyRing keyRing, final PGPPublicKey publicKey) - { - final PgpKeyId pgpKeyId = new PgpKeyId(publicKey.getKeyID()); - final PgpKeyFingerprint pgpKeyFingerprint = new PgpKeyFingerprint(publicKey.getFingerprint()); - - PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); - if (pgpKey == null) - { - pgpKey = new PgpKey(pgpKeyId, pgpKeyFingerprint); - pgpKeyFingerprint2pgpKey.put(pgpKeyFingerprint, pgpKey); - PgpKey old = pgpKeyId2PgpKey.put(pgpKeyId, pgpKey); - if (old != null) - throw new IllegalStateException( - String.format( - "PGP-key-ID collision! Two keys with different fingerprints have the same key-ID! keyId=%s fingerprint1=%s fingerprint2=%s", - pgpKeyId, old.getPgpKeyFingerprint(), pgpKey.getPgpKeyFingerprint())); - } - - if (keyRing instanceof PGPSecretKeyRing) - pgpKey.setSecretKeyRing((PGPSecretKeyRing) keyRing); - else if (keyRing instanceof PGPPublicKeyRing) - pgpKey.setPublicKeyRing((PGPPublicKeyRing) keyRing); - else - throw new IllegalArgumentException( - "keyRing is neither an instance of PGPSecretKeyRing nor PGPPublicKeyRing!"); - - pgpKey.setPublicKey(publicKey); - - if (publicKey.isMasterKey()) - { - masterKey = pgpKey; - pgpKeyId2masterKey.put(pgpKey.getPgpKeyId(), pgpKey); - } - else - { - if (masterKey == null) - throw new IllegalStateException("First key is a non-master key!"); - - pgpKey.setMasterKey(masterKey); - masterKey.getSubKeyIds().add(pgpKey.getPgpKeyId()); - } - return masterKey; - } + void markStale(); /** * Gets all those keys' fingerprints whose keys were signed (certified) by the key identified by the given @@ -394,26 +121,8 @@ else if (keyRing instanceof PGPPublicKeyRing) * @return the fingerprints of all those keys which have been signed (certified) by the key identified by * {@code signingPgpKeyFingerprint}. Never null, but maybe empty. */ - public synchronized Set getPgpKeyFingerprintsSignedBy( - final PgpKeyFingerprint signingPgpKeyFingerprint) - { - assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); - final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); - if (signingPgpKey == null) - return Collections.emptySet(); - - final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); - if (pgpKeyIds == null) - return Collections.emptySet(); - - final Set result = new HashSet<>(pgpKeyIds.size()); - for (final PgpKeyId pgpKeyId : pgpKeyIds) - { - final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); - result.add(pgpKey.getPgpKeyFingerprint()); - } - return Collections.unmodifiableSet(result); - } + Set getPgpKeyFingerprintsSignedBy( + PgpKeyFingerprint signingPgpKeyFingerprint); /** * Gets all those keys' IDs whose keys were signed (certified) by the key identified by the given ID. @@ -427,66 +136,7 @@ public synchronized Set getPgpKeyFingerprintsSignedBy( * @return the IDs of all those keys which have been signed (certified) by the key identified by * {@code signingPgpKeyId}. Never null, but maybe empty. */ - public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) - { - final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); - if (pgpKeyIds == null) - return Collections.emptySet(); - - return Collections.unmodifiableSet(pgpKeyIds); - } - - protected synchronized Map> getSigningKeyId2signedKeyIds() - { - loadIfNeeded(); - if (signingKeyId2signedKeyIds == null) - { - final Map> m = new HashMap<>(); - for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) - { - final PGPPublicKey publicKey = pgpKey.getPublicKey(); - for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) - { - if (pgpUserId.getUserId() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } else if (pgpUserId.getUserAttribute() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } else - throw new IllegalStateException("WTF?!"); - } - - // It seems, there are both: certifications for individual - // user-ids and certifications for the - // entire key. I therefore first take the individual ones - // (above) into account then and then - // the ones for the entire key (below). - // Normally, the signatures bound to the key are never - // 'certifications', but it rarely happens. - // Don't know, if these are malformed or deprecated (very old) - // keys, but I should take them into account. - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } - signingKeyId2signedKeyIds = m; - } - return signingKeyId2signedKeyIds; - } + Set getPgpKeyIdsSignedBy(PgpKeyId signingPgpKeyId); /** * Gets the signatures certifying the authenticity of the given user-ID. @@ -496,76 +146,7 @@ protected synchronized Map> getSigningKeyId2signedKeyIds * @return the certifications authenticating the given {@code pgpUserId}. Never null. Because every * user-ID is normally at least signed by the owning key, it is normally never empty, too. */ - public synchronized List getSignatures(final PgpUserId pgpUserId) - { - assertNotNull("pgpUserId", pgpUserId); - final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); - - final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); - - final List result = new ArrayList<>(); - if (pgpUserId.getUserId() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) - { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); - } - } - } - else if (pgpUserId.getUserAttribute() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) - { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); - } - } - } - else - throw new IllegalStateException("WTF?!"); - - // There are also key-signatures which are not for a certain indivdual user-id/-attribute, but for the entire key. - // See the comment in getSigningKeyId2signedKeyIds() above for more details. - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) - { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); - } - } - - return result; - } - - protected static Iterator nullToEmpty(final Iterator iterator) - { - if (iterator == null) - return Collections. emptyList().iterator(); - else - return iterator; - } - - private void enlistInSigningKey2signedKeyIds(final Map> signingKeyId2signedKeyIds, - final PgpKey pgpKey, final PGPSignature pgpSignature) - { - final PgpKeyId signingPgpKeyId = new PgpKeyId(pgpSignature.getKeyID()); - Set signedKeyIds = signingKeyId2signedKeyIds.get(signingPgpKeyId); - if (signedKeyIds == null) - { - signedKeyIds = new HashSet<>(); - signingKeyId2signedKeyIds.put(signingPgpKeyId, signedKeyIds); - } - signedKeyIds.add(pgpKey.getPgpKeyId()); - } + List getSignatures(PgpUserId pgpUserId); /** * Determines whether the given signature is a certification. @@ -577,11 +158,7 @@ private void enlistInSigningKey2signedKeyIds(final Map> * @return true, if the signature is a certification; false, if it is of a different type. * @see #isCertification(int) */ - public boolean isCertification(final PGPSignature pgpSignature) - { - assertNotNull("pgpSignature", pgpSignature); - return isCertification(pgpSignature.getSignatureType()); - } + boolean isCertification(PGPSignature pgpSignature); /** * Determines whether the given signature-type indicates a certification. @@ -594,11 +171,5 @@ public boolean isCertification(final PGPSignature pgpSignature) * @return true, if the given signature-type means certification; false otherwise. * @see #isCertification(PGPSignature) */ - public boolean isCertification(int pgpSignatureType) - { - return PGPSignature.DEFAULT_CERTIFICATION == pgpSignatureType - || PGPSignature.NO_CERTIFICATION == pgpSignatureType - || PGPSignature.CASUAL_CERTIFICATION == pgpSignatureType - || PGPSignature.POSITIVE_CERTIFICATION == pgpSignatureType; - } -} + boolean isCertification(int pgpSignatureType); +} \ No newline at end of file diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java new file mode 100644 index 0000000000..c311a5aa68 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java @@ -0,0 +1,500 @@ +package org.bouncycastle.openpgp.wot.key; + +import static org.bouncycastle.openpgp.wot.internal.Util.*; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link PgpKeyRegistry}. + * + * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + */ +public class PgpKeyRegistryImpl implements PgpKeyRegistry +{ + private static final Logger logger = LoggerFactory.getLogger(PgpKeyRegistryImpl.class); + + private final File pubringFile; + private final File secringFile; + + private long pubringFileLastModified = Long.MIN_VALUE; + private long secringFileLastModified = Long.MIN_VALUE; + + private Map pgpKeyFingerprint2pgpKey; // all keys + private Map pgpKeyId2pgpKey; // all keys + private Map pgpKeyId2masterKey; // only master-keys + + private Map> signingKeyId2signedKeyIds; + + /** + * Creates an instance of {@code PgpKeyRegistryImpl} with the given public and secret key ring collection files. + * + * @param pubringFile + * the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/} + * ). Must not be null. The file does not need to exist, though. + * @param secringFile + * the file containing the secret keys - usually named {@code secring.gpg} (located in {@code ~/.gnupg/} + * ). Must not be null. The file does not need to exist, though. + */ + public PgpKeyRegistryImpl(File pubringFile, File secringFile) + { + this.pubringFile = assertNotNull("pubringFile", pubringFile); + this.secringFile = assertNotNull("secringFile", secringFile); + } + + @Override + public File getPubringFile() + { + return pubringFile; + } + + @Override + public File getSecringFile() + { + return secringFile; + } + + @Override + public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentException + { + final PgpKey pgpKey = getPgpKey(pgpKeyId); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); + + return pgpKey; + } + + @Override + public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException + { + assertNotNull("pgpKeyId", pgpKeyId); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + return pgpKey; + } + + @Override + public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException + { + final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); + + return pgpKey; + } + + @Override + public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException + { + assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + return pgpKey; + } + + @Override + public synchronized Collection getMasterKeys() + { + loadIfNeeded(); + return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); + } + + @Override + public void markStale() + { + pubringFileLastModified = Long.MIN_VALUE; + secringFileLastModified = Long.MIN_VALUE; + } + + /** + * Loads the key ring files, if they were not yet read or if this registry is stale. + */ + protected synchronized void loadIfNeeded() + { + if (pgpKeyId2pgpKey == null + || getPubringFile().lastModified() != pubringFileLastModified + || getSecringFile().lastModified() != secringFileLastModified) + { + logger.debug("loadIfNeeded: invoking load()."); + load(); + } + else + logger.trace("loadIfNeeded: *not* invoking load()."); + } + + /** + * Loads the key ring files. + */ + protected synchronized void load() + { + pgpKeyFingerprint2pgpKey = null; + final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); + final Map pgpKeyId2pgpKey = new HashMap<>(); + final Map pgpKeyId2masterKey = new HashMap<>(); + + final long pubringFileLastModified; + final long secringFileLastModified; + try + { + final File secringFile = getSecringFile(); + logger.debug("load: secringFile='{}'", secringFile); + secringFileLastModified = secringFile.lastModified(); + if (secringFile.isFile()) + { + final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) + { + pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), + new BcKeyFingerprintCalculator()); + } + for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext();) + { + final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + + for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext();) + { + final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); + final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + if (pgpKey == null) + throw new IllegalStateException( + "Secret key does not have corresponding public key in secret key ring! pgpKeyId=" + + pgpKeyId); + + pgpKey.setSecretKey(secretKey); + logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); + } + } + } + + final File pubringFile = getPubringFile(); + logger.debug("load: pubringFile='{}'", pubringFile); + pubringFileLastModified = pubringFile.lastModified(); + if (pubringFile.isFile()) + { + final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) + { + pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), + new BcKeyFingerprintCalculator()); + } + + for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext();) + { + final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + } + } + } catch (IOException | PGPException x) + { + throw new RuntimeException(x); + } + + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) + { + if (pgpKey.getPublicKey() == null) + throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); + + if (pgpKey.getPublicKeyRing() == null) + throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); + } + + this.secringFileLastModified = secringFileLastModified; + this.pubringFileLastModified = pubringFileLastModified; + this.pgpKeyFingerprint2pgpKey = pgpKeyFingerprint2pgpKey; + this.pgpKeyId2pgpKey = pgpKeyId2pgpKey; + this.pgpKeyId2masterKey = pgpKeyId2masterKey; + + assignSubKeys(); + } + + private void assignSubKeys() + { + for (final PgpKey masterKey : pgpKeyId2masterKey.values()) + { + final Set subKeyIds = masterKey.getSubKeyIds(); + final List subKeys = new ArrayList(subKeyIds.size()); + for (final PgpKeyId subKeyId : subKeyIds) + { + final PgpKey subKey = getPgpKeyOrFail(subKeyId); + subKeys.add(subKey); + } + masterKey.setSubKeys(Collections.unmodifiableList(subKeys)); + masterKey.setSubKeyIds(Collections.unmodifiableSet(subKeyIds)); + } + } + + private PgpKey enlistPublicKey(final Map pgpKeyFingerprint2pgpKey, + final Map pgpKeyId2PgpKey, + final Map pgpKeyId2masterKey, + PgpKey masterKey, final PGPKeyRing keyRing, final PGPPublicKey publicKey) + { + final PgpKeyId pgpKeyId = new PgpKeyId(publicKey.getKeyID()); + final PgpKeyFingerprint pgpKeyFingerprint = new PgpKeyFingerprint(publicKey.getFingerprint()); + + PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + if (pgpKey == null) + { + pgpKey = new PgpKey(pgpKeyId, pgpKeyFingerprint); + pgpKeyFingerprint2pgpKey.put(pgpKeyFingerprint, pgpKey); + PgpKey old = pgpKeyId2PgpKey.put(pgpKeyId, pgpKey); + if (old != null) + throw new IllegalStateException( + String.format( + "PGP-key-ID collision! Two keys with different fingerprints have the same key-ID! keyId=%s fingerprint1=%s fingerprint2=%s", + pgpKeyId, old.getPgpKeyFingerprint(), pgpKey.getPgpKeyFingerprint())); + } + + if (keyRing instanceof PGPSecretKeyRing) + pgpKey.setSecretKeyRing((PGPSecretKeyRing) keyRing); + else if (keyRing instanceof PGPPublicKeyRing) + pgpKey.setPublicKeyRing((PGPPublicKeyRing) keyRing); + else + throw new IllegalArgumentException( + "keyRing is neither an instance of PGPSecretKeyRing nor PGPPublicKeyRing!"); + + pgpKey.setPublicKey(publicKey); + + if (publicKey.isMasterKey()) + { + masterKey = pgpKey; + pgpKeyId2masterKey.put(pgpKey.getPgpKeyId(), pgpKey); + } + else + { + if (masterKey == null) + throw new IllegalStateException("First key is a non-master key!"); + + pgpKey.setMasterKey(masterKey); + masterKey.getSubKeyIds().add(pgpKey.getPgpKeyId()); + } + return masterKey; + } + + /* (non-Javadoc) + * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#getPgpKeyFingerprintsSignedBy(org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint) + */ + @Override + public synchronized Set getPgpKeyFingerprintsSignedBy( + final PgpKeyFingerprint signingPgpKeyFingerprint) + { + assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); + final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); + if (signingPgpKey == null) + return Collections.emptySet(); + + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); + if (pgpKeyIds == null) + return Collections.emptySet(); + + final Set result = new HashSet<>(pgpKeyIds.size()); + for (final PgpKeyId pgpKeyId : pgpKeyIds) + { + final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); + result.add(pgpKey.getPgpKeyFingerprint()); + } + return Collections.unmodifiableSet(result); + } + + /* (non-Javadoc) + * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#getPgpKeyIdsSignedBy(org.bouncycastle.openpgp.wot.key.PgpKeyId) + */ + @Override + public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) + { + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); + if (pgpKeyIds == null) + return Collections.emptySet(); + + return Collections.unmodifiableSet(pgpKeyIds); + } + + protected synchronized Map> getSigningKeyId2signedKeyIds() + { + loadIfNeeded(); + if (signingKeyId2signedKeyIds == null) + { + final Map> m = new HashMap<>(); + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) + { + final PGPPublicKey publicKey = pgpKey.getPublicKey(); + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) + { + if (pgpUserId.getUserId() != null) + { + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } else if (pgpUserId.getUserAttribute() != null) + { + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } else + throw new IllegalStateException("WTF?!"); + } + + // It seems, there are both: certifications for individual + // user-ids and certifications for the + // entire key. I therefore first take the individual ones + // (above) into account then and then + // the ones for the entire key (below). + // Normally, the signatures bound to the key are never + // 'certifications', but it rarely happens. + // Don't know, if these are malformed or deprecated (very old) + // keys, but I should take them into account. + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } + signingKeyId2signedKeyIds = m; + } + return signingKeyId2signedKeyIds; + } + + /* (non-Javadoc) + * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#getSignatures(org.bouncycastle.openpgp.wot.key.PgpUserId) + */ + @Override + public synchronized List getSignatures(final PgpUserId pgpUserId) + { + assertNotNull("pgpUserId", pgpUserId); + final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); + + final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); + + final List result = new ArrayList<>(); + if (pgpUserId.getUserId() != null) + { + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else if (pgpUserId.getUserAttribute() != null) + { + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else + throw new IllegalStateException("WTF?!"); + + // There are also key-signatures which are not for a certain indivdual user-id/-attribute, but for the entire key. + // See the comment in getSigningKeyId2signedKeyIds() above for more details. + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + + return result; + } + + protected static Iterator nullToEmpty(final Iterator iterator) + { + if (iterator == null) + return Collections. emptyList().iterator(); + else + return iterator; + } + + private void enlistInSigningKey2signedKeyIds(final Map> signingKeyId2signedKeyIds, + final PgpKey pgpKey, final PGPSignature pgpSignature) + { + final PgpKeyId signingPgpKeyId = new PgpKeyId(pgpSignature.getKeyID()); + Set signedKeyIds = signingKeyId2signedKeyIds.get(signingPgpKeyId); + if (signedKeyIds == null) + { + signedKeyIds = new HashSet<>(); + signingKeyId2signedKeyIds.put(signingPgpKeyId, signedKeyIds); + } + signedKeyIds.add(pgpKey.getPgpKeyId()); + } + + /* (non-Javadoc) + * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#isCertification(org.bouncycastle.openpgp.PGPSignature) + */ + @Override + public boolean isCertification(final PGPSignature pgpSignature) + { + assertNotNull("pgpSignature", pgpSignature); + return isCertification(pgpSignature.getSignatureType()); + } + + /* (non-Javadoc) + * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#isCertification(int) + */ + @Override + public boolean isCertification(int pgpSignatureType) + { + return PGPSignature.DEFAULT_CERTIFICATION == pgpSignatureType + || PGPSignature.NO_CERTIFICATION == pgpSignatureType + || PGPSignature.CASUAL_CERTIFICATION == pgpSignatureType + || PGPSignature.POSITIVE_CERTIFICATION == pgpSignatureType; + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java index f0298bf82f..3b1de62fb2 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/package-info.java @@ -1,6 +1,6 @@ /** * Key management used by the OpenPGP Web Of Trust (WOT) implementation. *

- * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.key.PgpKeyRegistry PgpKeyRegistry}. + * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.key.PgpKeyRegistryImpl PgpKeyRegistryImpl}. */ package org.bouncycastle.openpgp.wot.key; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java index a2f0c6213e..332c84a27f 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java @@ -1,7 +1,7 @@ /** * OpenPGP Web Of Trust (WOT) implementation. *

- * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.TrustDb TrustDb}. + * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.TrustDbImpl TrustDbImpl}. */ package org.bouncycastle.openpgp.wot; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java index 14ae081d76..a9efa60baa 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/AbstractTrustDbTest.java @@ -49,6 +49,7 @@ import org.bouncycastle.openpgp.wot.key.PgpKey; import org.bouncycastle.openpgp.wot.key.PgpKeyId; import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; +import org.bouncycastle.openpgp.wot.key.PgpKeyRegistryImpl; import org.bouncycastle.openpgp.wot.key.PgpUserId; import org.junit.After; import org.junit.Before; @@ -93,7 +94,7 @@ public void before() throws Exception { secringFile = new File(gnupgHomeDir, "secring.gpg"); trustdbFile = new File(gnupgHomeDir, "trustdb.gpg"); - pgpKeyRegistry = new PgpKeyRegistry(pubringFile, secringFile); + pgpKeyRegistry = new PgpKeyRegistryImpl(pubringFile, secringFile); } @After diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java index 95d648bc89..a87c9aa1b8 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java @@ -99,7 +99,7 @@ public void readBrokenRecord() throws Exception { @Test public void isExpired() throws Exception { PgpKey pgpKey = pgpKeyRegistry.getPgpKey(new PgpKeyId("56422A5E710E3371")); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.isExpired(pgpKey.getPublicKey()); } } @@ -110,7 +110,7 @@ public void updateMyProductiveTrustDb() throws Exception { Map pgpUserId2ValidityOriginal = getPgpUserId2Validity(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); } @@ -169,7 +169,7 @@ else if ((originalValidity & TRUST_FLAG_DISABLED) != 0 && (myValidity & TRUST_FL private Map getPgpUserId2Validity() throws Exception { Map pgpUserId2Validity = new HashMap<>(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { for (PgpKey pgpKey : pgpKeyRegistry.getMasterKeys()) { for (PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { // if (pgpUserId.getUserId() != null) { diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java index 2fbe37d91a..16f14394a5 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java @@ -17,21 +17,21 @@ public void directOnly() throws Exception { bobKey = signPublicKey(aliceKey, POSITIVE_CERTIFICATION, bobKey); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); } if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -58,7 +58,7 @@ public void oneIndirection() throws Exception { georgKey = signPublicKey(frankKey, POSITIVE_CERTIFICATION, georgKey); // georg <= frank <= alice hansKey = signPublicKey(bobKey, CASUAL_CERTIFICATION, hansKey); // hans <= bob <= alice - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); trustDb.setOwnerTrust(bobKey.getPublicKey(), OwnerTrust.FULLY); @@ -69,7 +69,7 @@ public void oneIndirection() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); @@ -80,7 +80,7 @@ public void oneIndirection() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -120,7 +120,7 @@ public void twoIndirections() throws Exception { johnKey = signPublicKey(frankKey, POSITIVE_CERTIFICATION, johnKey); // john <= frank <= bob <= alice - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); trustDb.setOwnerTrust(bobKey.getPublicKey(), OwnerTrust.MARGINAL); @@ -137,7 +137,7 @@ public void twoIndirections() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -152,7 +152,7 @@ public void twoIndirections() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -179,7 +179,7 @@ public void twoIndirections() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -189,7 +189,7 @@ public void twoIndirections() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -209,7 +209,7 @@ public void twoIndirections() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -219,7 +219,7 @@ public void twoIndirections() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -239,7 +239,7 @@ public void twoIndirections() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -249,7 +249,7 @@ public void twoIndirections() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -305,7 +305,7 @@ public void threeIndirections() throws Exception { for (PgpKey signingKey : level3Keys) signPublicKey(signingKey, POSITIVE_CERTIFICATION, karlKey); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); for (PgpKey key : level1Keys) @@ -322,7 +322,7 @@ public void threeIndirections() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -341,7 +341,7 @@ public void threeIndirections() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); @@ -368,7 +368,7 @@ public void threeIndirections() throws Exception { if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); @@ -387,7 +387,7 @@ public void threeIndirections() throws Exception { } } - try (TrustDb trustDb = new TrustDb(trustdbFile, pgpKeyRegistry);) { + try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); From 51e0e55a78ee74cd9fd700e67e6e759aaaa09295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 18:48:54 +0700 Subject: [PATCH 10/17] Consistency adjective / adverb. --- .../bouncycastle/openpgp/wot/OwnerTrust.java | 2 +- .../bouncycastle/openpgp/wot/TrustConst.java | 2 +- .../bouncycastle/openpgp/wot/TrustDbImpl.java | 10 +- .../bouncycastle/openpgp/wot/Validity.java | 2 +- .../openpgp/wot/UpdateTrustDbTest.java | 140 +++++++++--------- 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java index f3cbe998ff..d290f6326b 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java @@ -53,7 +53,7 @@ public enum OwnerTrust * This causes significant transitive trust. Depending on the settings, this is already enough for a certified key * to be trusted fully, or it might require further signatures. */ - FULLY(TrustConst.TRUST_FULLY), // 5 + FULL(TrustConst.TRUST_FULL), // 5 /** * The key's owner can be ultimately trusted. One single signature of a notary whose key is marked 'ultimate' is diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java index 80f6493971..c5792e18b1 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustConst.java @@ -22,7 +22,7 @@ public interface TrustConst /** m: marginally trusted */ int TRUST_MARGINAL = 4; /** f: fully trusted */ - int TRUST_FULLY = 5; + int TRUST_FULL = 5; /** u: ultimately trusted */ int TRUST_ULTIMATE = 6; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java index 8699ce8c34..2cc0018eb4 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java @@ -642,7 +642,7 @@ public synchronized void updateTrustDb() updateValidity(pgpUserId, depth, validity, pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); - if (validity >= TRUST_FULLY) + if (validity >= TRUST_FULL) fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); } @@ -783,7 +783,7 @@ private void validateKey(final PgpKey pgpKey) case ULTIMATE: pgpUserIdTrust.incUltimateCount(); break; - case FULLY: + case FULL: pgpUserIdTrust.incFullCount(); break; case MARGINAL: @@ -795,11 +795,11 @@ private void validateKey(final PgpKey pgpKey) } if (pgpUserIdTrust.getUltimateCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_FULLY); + pgpUserIdTrust.setValidity(TRUST_FULL); else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULLY); + pgpUserIdTrust.setValidity(TRUST_FULL); else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULLY); + pgpUserIdTrust.setValidity(TRUST_FULL); else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) pgpUserIdTrust.setValidity(TRUST_MARGINAL); } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java index ba70f0623d..82e2518f21 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java @@ -46,7 +46,7 @@ public enum Validity * A user should see some positive confirmation (e.g. a green indication colour) when using or encountering such a * key. */ - FULLY(TrustConst.TRUST_FULLY), // 5 + FULLY(TrustConst.TRUST_FULL), // 5 /** * The key/user-identity/user-attribute is definitely valid - probably it's belonging to the user himself. There is diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java index 16f14394a5..1661ba218e 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java @@ -26,7 +26,7 @@ public void directOnly() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } @@ -34,7 +34,7 @@ public void directOnly() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); } } @@ -61,8 +61,8 @@ public void oneIndirection() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.setOwnerTrust(aliceKey.getPublicKey(), OwnerTrust.ULTIMATE); - trustDb.setOwnerTrust(bobKey.getPublicKey(), OwnerTrust.FULLY); - trustDb.setOwnerTrust(cathrinKey.getPublicKey(), OwnerTrust.FULLY); + trustDb.setOwnerTrust(bobKey.getPublicKey(), OwnerTrust.FULL); + trustDb.setOwnerTrust(cathrinKey.getPublicKey(), OwnerTrust.FULL); trustDb.setOwnerTrust(frankKey.getPublicKey(), OwnerTrust.MARGINAL); } @@ -71,24 +71,24 @@ public void oneIndirection() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // the signature type (CASUAL) has no effect :-( + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); // the signature type (CASUAL) has no effect :-( } } try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_UNKNOWN); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); // behave like GnuPG! + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); // behave like GnuPG! } } @@ -138,10 +138,10 @@ public void twoIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -155,10 +155,10 @@ public void twoIndirections() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -210,8 +210,8 @@ public void twoIndirections() throws Exception { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -222,8 +222,8 @@ public void twoIndirections() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -233,31 +233,31 @@ public void twoIndirections() throws Exception { hansKey = signPublicKey(cathrinKey, POSITIVE_CERTIFICATION, hansKey); // hans <= cathrin+daniel+emil <= alice // UNCHANGED: john <= frank+georg+hans <= bob+cathrin+daniel+emil <= alice - // only the validity of hans' key changed from MARGINAL to FULLY - and is now taken into account. - // => now there are 3 marginal signatures for john => it changes from MARGINAL to FULLY, too. + // only the validity of hans' key changed from MARGINAL to FULL - and is now taken into account. + // => now there are 3 marginal signatures for john => it changes from MARGINAL to FULL, too. if (! SKIP_GPG_CHECK_TRUST_DB) { runGpgCheckTrustDb(); try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULL); } } try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { trustDb.updateTrustDb(); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); - assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULL); } } @@ -325,16 +325,16 @@ public void threeIndirections() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -346,16 +346,16 @@ public void threeIndirections() throws Exception { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULL); assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_MARGINAL); @@ -371,19 +371,19 @@ public void threeIndirections() throws Exception { try (TrustDb trustDb = new TrustDbImpl(trustdbFile, pgpKeyRegistry);) { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_FULL); } } @@ -392,19 +392,19 @@ public void threeIndirections() throws Exception { assertThat(trustDb.getValidityRaw(aliceKey.getPublicKey())).isEqualTo(TRUST_ULTIMATE); - assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(bobKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(cathrinKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(danielKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(emilKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(frankKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(georgKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULLY); - assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(hansKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(idaKey.getPublicKey())).isEqualTo(TRUST_FULL); + assertThat(trustDb.getValidityRaw(johnKey.getPublicKey())).isEqualTo(TRUST_FULL); - assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_FULLY); + assertThat(trustDb.getValidityRaw(karlKey.getPublicKey())).isEqualTo(TRUST_FULL); } } } From 79943ea0312a695503545d5d7401859b239f0cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Mon, 21 Sep 2015 19:03:59 +0700 Subject: [PATCH 11/17] Again consistency adjective / adverb. --- pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java index 82e2518f21..ee5e4667c5 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java @@ -46,7 +46,7 @@ public enum Validity * A user should see some positive confirmation (e.g. a green indication colour) when using or encountering such a * key. */ - FULLY(TrustConst.TRUST_FULL), // 5 + FULL(TrustConst.TRUST_FULL), // 5 /** * The key/user-identity/user-attribute is definitely valid - probably it's belonging to the user himself. There is From 54101514d46d578bfd96855318d80a1f6274ea99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Sat, 26 Sep 2015 09:12:13 +0700 Subject: [PATCH 12/17] Changed contract of TrustDb.getOwnerTrust(...) to return null, if there was none assigned, yet (consistent with behaviour in Thunderbird/Enigmail - which shows different values for non-assigned and UNKNOWN). --- .../src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java | 6 ++---- .../main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java index 93dda6e408..208e1babf0 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java @@ -33,8 +33,7 @@ public interface TrustDb extends AutoCloseable * * @param pgpKey * the key whose owner-trust should be looked up. Must not be null. - * @return the owner-trust. Never null. If none has been assigned, before, this method returns - * {@link OwnerTrust#UNKNOWN UNKNOWN}. + * @return the owner-trust. May be null, if none has been assigned, before. * @see #setOwnerTrust(PgpKey, OwnerTrust) * @see #getOwnerTrust(PGPPublicKey) */ @@ -67,8 +66,7 @@ public interface TrustDb extends AutoCloseable * * @param publicKey * the key whose owner-trust should be looked up. Must not be null. - * @return the owner-trust. Never null. If none has been assigned, before, this method returns - * {@link OwnerTrust#UNKNOWN UNKNOWN}. + * @return the owner-trust. May be null, if none has been assigned, before. * @see #setOwnerTrust(PGPPublicKey, OwnerTrust) * @see #getOwnerTrust(PgpKey) */ diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java index 2cc0018eb4..fe8e7b73b4 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java @@ -162,7 +162,7 @@ public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) TrustRecord.Trust trust = getTrustByPublicKey(publicKey); if (trust == null) - return OwnerTrust.UNKNOWN; + return null; return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); } From b91fbb31943b3e17bcbb2f100b39c7bf99310905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Sat, 26 Sep 2015 09:15:11 +0700 Subject: [PATCH 13/17] Minor change in javadoc. --- pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java index d290f6326b..8bb6727ffe 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java @@ -12,6 +12,7 @@ *

  • How well does this person understand OpenPGP? *
  • How well does this person protect his computer's integrity and most importantly his private key? *
  • How well does this person check the authenticity of another key before signing (= certifying) it? + *
  • Would this person certify a key in bad faith? * *

    * Whether you trust this person on a personal level, should have only a minor influence on the owner-trust. For From e01f69fca3a5c013dd863f3c5597adc0cf34c031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Thu, 1 Oct 2015 14:29:19 +0700 Subject: [PATCH 14/17] Applied feedback from David Hook: * Removed LICENSE (it's in the parent-directory, anyway). * Made thread-safety bullet-proof. --- pgwot/LICENSE | 24 - .../org/bouncycastle/openpgp/wot/Config.java | 2 - .../bouncycastle/openpgp/wot/OwnerTrust.java | 7 +- .../org/bouncycastle/openpgp/wot/TrustDb.java | 33 +- .../openpgp/wot/TrustDbException.java | 4 +- .../bouncycastle/openpgp/wot/TrustDbImpl.java | 807 ------------- .../openpgp/wot/TrustDbIoException.java | 2 - .../bouncycastle/openpgp/wot/TrustModel.java | 4 +- .../bouncycastle/openpgp/wot/Validity.java | 6 +- .../openpgp/wot/internal/Mutex.java | 56 + .../openpgp/wot/internal/PgpKeyTrust.java | 2 +- .../openpgp/wot/internal/PgpUserIdTrust.java | 2 +- .../openpgp/wot/internal/TrustDbImpl.java | 865 ++++++++++++++ .../openpgp/wot/internal/TrustDbIo.java | 1005 +++++++++-------- .../openpgp/wot/internal/TrustRecord.java | 103 +- .../openpgp/wot/internal/TrustRecordType.java | 4 +- .../openpgp/wot/internal/Util.java | 15 + .../bouncycastle/openpgp/wot/key/PgpKey.java | 35 +- .../openpgp/wot/key/PgpKeyFingerprint.java | 8 +- .../openpgp/wot/key/PgpKeyId.java | 6 +- .../openpgp/wot/key/PgpKeyRegistry.java | 25 +- .../openpgp/wot/key/PgpKeyRegistryImpl.java | 444 ++++---- .../openpgp/wot/key/PgpUserId.java | 4 +- .../openpgp/wot/key/PgpUserIdNameHash.java | 8 +- .../openpgp/wot/package-info.java | 2 +- .../wot/TrustDbProductiveFileTest.java | 11 +- .../openpgp/wot/UpdateTrustDbTest.java | 1 + 27 files changed, 1828 insertions(+), 1657 deletions(-) delete mode 100644 pgwot/LICENSE delete mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Mutex.java create mode 100644 pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbImpl.java diff --git a/pgwot/LICENSE b/pgwot/LICENSE deleted file mode 100644 index ea7fb12024..0000000000 --- a/pgwot/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Please note this should be read in the same way as the MIT license (http://opensource.org/licenses/MIT). - -*************** -*** License *** -*************** - -Copyright (c) 2000 - 2015 - * The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - * CodeWizards GmbH (http://codewizards.co) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java index 816b4fa95c..2a9d95bd5e 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Config.java @@ -2,8 +2,6 @@ /** * Configuration settings for the trust calculation. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class Config implements TrustConst { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java index 8bb6727ffe..d890726583 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/OwnerTrust.java @@ -3,6 +3,8 @@ import java.util.HashMap; import java.util.Map; +import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; + /** * The owner-trust is assigned to a key, but does not describe the key itself. It specifies how reliable the * owner of this key is in his function as notary certifying other keys. @@ -18,8 +20,7 @@ * Whether you trust this person on a personal level, should have only a minor influence on the owner-trust. For * example, you certainly trust your mother, but unless she's really computer-savvy, you likely assign a low owner-trust * to her key. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co + * @see TrustDbImpl#getOwnerTrust(org.bouncycastle.openpgp.wot.key.PgpKey) */ public enum OwnerTrust @@ -67,7 +68,7 @@ public enum OwnerTrust private final int numericValue; - private static Map numericValue2OwnerTrust; + private static volatile Map numericValue2OwnerTrust; private OwnerTrust(final int numericValue) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java index 208e1babf0..a25a5a5273 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDb.java @@ -1,7 +1,13 @@ package org.bouncycastle.openpgp.wot; +import static org.bouncycastle.openpgp.wot.internal.Util.*; + +import java.io.File; + import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; import org.bouncycastle.openpgp.wot.key.PgpUserId; import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; @@ -17,11 +23,34 @@ *

  • Set a key's {@linkplain #setOwnerTrust(PGPPublicKey, int) owner-trust} attribute. *
  • {@linkplain #updateTrustDb() Recalculate the web-of-trust}. * - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public interface TrustDb extends AutoCloseable { + /** + * Utility class for creating an instance of a {@code TrustDb} implementation. + */ + public static class Helper { + /** + * Creates a new instance of a {@code TrustDb} implementation. + *

    + * Important: You must {@linkplain TrustDb#close() close} this instance! + *

    + * There is currently only one single implementation available ({@code TrustDbImpl}), + * but this might change in the future. Hence, this method should be used instead of + * directly invoking a constructor! + * @param file + * the trust-database-file ({@code trustdb.gpg}). Must not be null. + * @param pgpKeyRegistry + * the key-registry. Must not be null. + * @return a new instance of a {@code TrustDb}. Never null. + */ + public static TrustDb createInstance(final File file, final PgpKeyRegistry pgpKeyRegistry) { + assertNotNull("file", file); + assertNotNull("pgpKeyRegistry", pgpKeyRegistry); + return new TrustDbImpl(file, pgpKeyRegistry); + } + } + @Override void close(); diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java index e13434a48c..91550a30aa 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbException.java @@ -1,9 +1,9 @@ package org.bouncycastle.openpgp.wot; +import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; + /** * Exception thrown by {@link TrustDbImpl} and related classes. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class TrustDbException extends RuntimeException { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java deleted file mode 100644 index fe8e7b73b4..0000000000 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbImpl.java +++ /dev/null @@ -1,807 +0,0 @@ -package org.bouncycastle.openpgp.wot; - -import static org.bouncycastle.openpgp.wot.internal.Util.*; - -import java.io.File; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.wot.internal.PgpKeyTrust; -import org.bouncycastle.openpgp.wot.internal.PgpUserIdTrust; -import org.bouncycastle.openpgp.wot.internal.TrustDbIo; -import org.bouncycastle.openpgp.wot.internal.TrustRecord; -import org.bouncycastle.openpgp.wot.internal.TrustRecordType; -import org.bouncycastle.openpgp.wot.key.PgpKey; -import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; -import org.bouncycastle.openpgp.wot.key.PgpKeyId; -import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; -import org.bouncycastle.openpgp.wot.key.PgpUserId; -import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * API implementation for working with GnuPG's {@code trustdb.gpg}. - *

    - * This class was mostly ported from the GnuPG's {@code trustdb.h} and {@code trustdb.c} files. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co - */ -public class TrustDbImpl implements AutoCloseable, TrustConst, TrustDb -{ - private static final Logger logger = LoggerFactory.getLogger(TrustDbImpl.class); - - private final TrustDbIo trustDbIo; - private final PgpKeyRegistry pgpKeyRegistry; - - private long startTime; - private long nextExpire; - private Map fingerprint2PgpKeyTrust; - private Set klist; - private Set fullTrust; - private DateFormat dateFormatIso8601WithTime; - - /** - * Create a {@code TrustDbImpl} instance with the given {@code trustdb.gpg} file and the given key-registry. - *

    - * Important: You must {@linkplain #close() close} this instance! - * - * @param file - * the trust-database-file ({@code trustdb.gpg}). Must not be null. - * @param pgpKeyRegistry - * the key-registry. Must not be null. - */ - public TrustDbImpl(final File file, final PgpKeyRegistry pgpKeyRegistry) - { - assertNotNull("file", file); - this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); - this.trustDbIo = new TrustDbIo(file); - } - - @Override - public void close() - { - trustDbIo.close(); - } - - public DateFormat getDateFormatIso8601WithTime() - { - if (dateFormatIso8601WithTime == null) - dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - - return dateFormatIso8601WithTime; - } - - protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) - { - PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); - if (pgpKeyTrust == null) - { - pgpKeyTrust = new PgpKeyTrust(pgpKey); - fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); - } - return pgpKeyTrust; - } - - // reset_trust_records(void) - protected void resetTrustRecords() - { - TrustRecord record; - long recordNum = 0; - int count = 0, nreset = 0; - - while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) - { - if (record.getType() == TrustRecordType.TRUST) - { - final TrustRecord.Trust trust = (TrustRecord.Trust) record; - ++count; - if (trust.getMinOwnerTrust() != 0) - { - trust.setMinOwnerTrust((short) 0); - trustDbIo.putTrustRecord(record); - } - } - else if (record.getType() == TrustRecordType.VALID) - { - final TrustRecord.Valid valid = (TrustRecord.Valid) record; - if (((valid.getValidity() & TRUST_MASK) != 0) - || valid.getMarginalCount() != 0 - || valid.getFullCount() != 0) - { - - valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); - valid.setMarginalCount((short) 0); - valid.setFullCount((short) 0); - nreset++; - trustDbIo.putTrustRecord(record); - } - } - } - - logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); - } - - @Override - public OwnerTrust getOwnerTrust(PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - return getOwnerTrust(pgpKey.getPublicKey()); - } - - @Override - public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) - { - assertNotNull("pgpKey", pgpKey); - assertNotNull("ownerTrust", ownerTrust); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); - } - - @Override - public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - // if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) - // return TRUST_UNKNOWN; // TODO maybe we should support other trust models... - - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - return null; - - return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); - } - - @Override - public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) - { - assertNotNull("publicKey", publicKey); - assertNotNull("ownerTrust", ownerTrust); - - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - { - // No record yet - create a new one. - trust = new TrustRecord.Trust(); - trust.setFingerprint(publicKey.getFingerprint()); - } - - int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; - - trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); - trustDbIo.putTrustRecord(trust); - - markTrustDbStale(); - trustDbIo.flush(); - } - - protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); - return trust; - } - - @Override - @Deprecated - public synchronized int getValidityRaw(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - return _getValidity(publicKey, (PgpUserIdNameHash) null, true); - } - - @Override - @Deprecated - public synchronized int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) - { - assertNotNull("publicKey", publicKey); - assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); - return _getValidity(publicKey, pgpUserIdNameHash, true); - } - - @Override - public Validity getValidity(final PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - return getValidity(pgpKey.getPublicKey()); - } - - @Override - public Validity getValidity(final PgpUserId pgpUserId) - { - assertNotNull("pgpUserId", pgpUserId); - return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); - } - - @Override - public Validity getValidity(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); - return Validity.fromNumericValue(numericValue); - } - - @Override - public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) - { - assertNotNull("publicKey", publicKey); - assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); - final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); - return Validity.fromNumericValue(numericValue); - } - - /** - * Ported from - * {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} - */ - protected synchronized int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, - final boolean withFlags) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = getTrustByPublicKey(publicKey); - if (trust == null) - return TRUST_UNKNOWN; - - // Loop over all user IDs - long recordNum = trust.getValidList(); - int validity = 0; - int flags = 0; - // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust - // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, - // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). - while (recordNum != 0) - { - TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); - assertNotNull("valid", valid); - - if (pgpUserIdNameHash != null) - { - // If a user ID is given we return the validity for that - // user ID ONLY. If the namehash is not found, then there - // is no validity at all (i.e. the user ID wasn't signed). - if (pgpUserIdNameHash.equals(valid.getNameHash())) - { - validity = valid.getValidity() & TRUST_MASK; - flags = valid.getValidity() & ~TRUST_MASK; - break; - } - } - else - { - // If no user ID is given, we take the maximum validity over all user IDs - validity = Math.max(validity, valid.getValidity() & TRUST_MASK); - flags |= valid.getValidity() & ~TRUST_MASK; - } - recordNum = valid.getNext(); - } - - if (withFlags) - { - validity |= flags; - - if ((trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0) - validity |= TRUST_FLAG_DISABLED; - - if (publicKey.hasRevocation()) - validity |= TRUST_FLAG_REVOKED; - - if (isTrustDbStale()) - validity |= TRUST_FLAG_PENDING_CHECK; - } - return validity; - } - - // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) - protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) - { - assertNotNull("pgpUserId", pgpUserId); - assertNonNegativeShort("depth", depth); - assertNonNegativeShort("validity", validity); - assertNonNegativeShort("fullCount", fullCount); - assertNonNegativeShort("marginalCount", marginalCount); - - TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); - if (trust == null) - { - // No record yet - create a new one. - trust = new TrustRecord.Trust(); - trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); - trustDbIo.putTrustRecord(trust); - } - - TrustRecord.Valid valid = null; - - // locate an existing Valid record - final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); - long recordNum = trust.getValidList(); - while (recordNum != 0) - { - valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); - if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) - break; - - recordNum = valid.getNext(); - } - - if (recordNum == 0) - { // insert a new validity record - valid = new TrustRecord.Valid(); - valid.setNameHash(pgpUserIdNameHashBytes); - valid.setNext(trust.getValidList()); - trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record - trust.setValidList(valid.getRecordNum()); - } - - valid.setValidity((short) validity); - valid.setFullCount((short) fullCount); - valid.setMarginalCount((short) marginalCount); - trust.setDepth((short) depth); - trustDbIo.putTrustRecord(trust); - trustDbIo.putTrustRecord(valid); - } - - private static void assertNonNegativeShort(final String name, final int value) - { - assertNotNull("name", name); - - if (value < 0) - throw new IllegalArgumentException(name + " < 0"); - - if (value > Short.MAX_VALUE) - throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); - } - - /* (non-Javadoc) - * @see org.bouncycastle.openpgp.wot.TrustDb#updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean) - */ - @Override - public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) - { - for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) - { - if (masterKey.getSecretKey() == null) - continue; - - TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); - if (trust == null - || trust.getOwnerTrust() == TRUST_UNKNOWN - || !onlyIfMissing) - { - - if (trust == null) - { - trust = new TrustRecord.Trust(); - trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); - } - - trust.setDepth((short) 0); - trust.setOwnerTrust((short) TRUST_ULTIMATE); - trustDbIo.putTrustRecord(trust); - } - } - } - - protected Set getUltimatelyTrustedKeyFingerprints() - { - Set result = new HashSet(); - TrustRecord record; - long recordNum = 0; - while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) - { - if (record.getType() == TrustRecordType.TRUST) - { - TrustRecord.Trust trust = (TrustRecord.Trust) record; - if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) - result.add(new PgpKeyFingerprint(trust.getFingerprint())); - } - } - return result; - } - - @Override - public boolean isExpired(PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - - final Date creationTime = publicKey.getCreationTime(); - - final long validSeconds = publicKey.getValidSeconds(); - if (validSeconds != 0) - { - long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); - return validUntilTimestamp < System.currentTimeMillis(); - } - return false; - // TODO there seem to be keys (very old keys) that seem to encode the validity differently. - // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it - // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. - // It's a very small number of keys only, hence I ignore it for now ;-) - } - - @Override - public boolean isDisabled(PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - return isDisabled(pgpKey.getPublicKey()); - } - - @Override - public void setDisabled(PgpKey pgpKey, final boolean disabled) - { - assertNotNull("pgpKey", pgpKey); - if (pgpKey.getMasterKey() != null) - pgpKey = pgpKey.getMasterKey(); - - setDisabled(pgpKey.getPublicKey(), disabled); - } - - @Override - public boolean isDisabled(final PGPPublicKey publicKey) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); - if (trust == null) - return false; - - return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; - } - - @Override - public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) - { - assertNotNull("publicKey", publicKey); - TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); - if (trust == null) - { - trust = new TrustRecord.Trust(); - trust.setFingerprint(publicKey.getFingerprint()); - } - - int ownerTrust = trust.getOwnerTrust(); - if (disabled) - ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; - else - ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; - - trust.setOwnerTrust((short) ownerTrust); - - trustDbIo.putTrustRecord(trust); - trustDbIo.flush(); - } - - @Override - public synchronized boolean isTrustDbStale() - { - final Config config = Config.getInstance(); - final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - if (config.getTrustModel() != version.getTrustModel()) - { - TrustModel configTrustModel; - try - { - configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); - } catch (IllegalArgumentException x) - { - configTrustModel = null; - } - - TrustModel versionTrustModel; - try - { - versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); - } catch (IllegalArgumentException x) - { - versionTrustModel = null; - } - - logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", - config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); - - return true; - } - - if (config.getCompletesNeeded() != version.getCompletesNeeded()) - { - logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", - config.getCompletesNeeded(), version.getCompletesNeeded()); - - return true; - } - - if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) - { - logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", - config.getMarginalsNeeded(), version.getMarginalsNeeded()); - - return true; - } - - if (config.getMaxCertDepth() != version.getCertDepth()) - { - logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", - config.getMaxCertDepth(), version.getCertDepth()); - - return true; - } - - final Date now = new Date(); - if (version.getNextCheck().before(now)) - { - logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", - getDateFormatIso8601WithTime().format(version.getNextCheck()), - getDateFormatIso8601WithTime().format(now)); - - return true; - } - - logger.trace("isTrustDbStale: stale=false"); - return false; - } - - @Override - public synchronized void markTrustDbStale() - { - final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - version.setNextCheck(new Date(0)); - trustDbIo.putTrustRecord(version); - } - - @Override - public synchronized void updateTrustDbIfNeeded() - { - if (isTrustDbStale()) - updateTrustDb(); - } - - /** - * {@inheritDoc} - *

    - * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, because the - * implementation looked overly complicated. This method here is a re-implementation from scratch. It still seems to - * come very closely to the behaviour of GnuPG's original code. - */ - @Override - public synchronized void updateTrustDb() - { - final Config config = Config.getInstance(); - try - { - fingerprint2PgpKeyTrust = new HashMap<>(); - fullTrust = new HashSet<>(); - - startTime = System.currentTimeMillis() / 1000; - nextExpire = Long.MAX_VALUE; - - resetTrustRecords(); - - final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); - if (ultimatelyTrustedKeyFingerprints.isEmpty()) - { - logger.warn("updateTrustDb: There are no ultimately trusted keys!"); - return; - } - - // mark all UTKs as used and fully_trusted and set validity to ultimate - for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) - { - final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); - if (utk == null) - { - logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); - continue; - } - - fullTrust.add(utkFpr); - - for (PgpUserId pgpUserId : utk.getPgpUserIds()) - updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); - - final long expireDate = getExpireTimestamp(utk.getPublicKey()); - if (expireDate >= startTime && expireDate < nextExpire) - nextExpire = expireDate; - } - - klist = ultimatelyTrustedKeyFingerprints; - - for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) - { - final List validatedKeys = validateKeyList(); - - klist = new HashSet<>(); - for (PgpKey pgpKey : validatedKeys) - { - PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); - klist.add(pgpKey.getPgpKeyFingerprint()); - - for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) - { - final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); - - final int validity = pgpUserIdTrust.getValidity(); - updateValidity(pgpUserId, depth, validity, - pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); - - if (validity >= TRUST_FULL) - fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); - } - - final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); - if (expireDate >= startTime && expireDate < nextExpire) - nextExpire = expireDate; - } - - logger.debug("updateTrustDb: depth={} keys={}", - depth, validatedKeys.size()); - } - - final Date nextExpireDate = new Date(nextExpire * 1000); - trustDbIo.updateVersionRecord(nextExpireDate); - - trustDbIo.flush(); - - logger.info("updateTrustDb: Next trust-db expiration date: {}", - getDateFormatIso8601WithTime().format(nextExpireDate)); - } finally - { - fingerprint2PgpKeyTrust = null; - klist = null; - fullTrust = null; - } - } - - private long getExpireTimestamp(PGPPublicKey pk) - { - final long validSeconds = pk.getValidSeconds(); - if (validSeconds == 0) - return Long.MAX_VALUE; - - final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; - return result; - } - - /** - * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, - * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see - * {@link #updateTrustDb()}. - * - * @return the keys that were processed by this method. - */ - private List validateKeyList() - { - final List result = new ArrayList<>(); - final Set signedPgpKeyFingerprints = new HashSet<>(); - for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) - signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); - - signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted - - for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) - { - final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); - if (pgpKey == null) - { - logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); - continue; - } - result.add(pgpKey); - validateKey(pgpKey); - } - return result; - } - - /** - * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, - * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. - * - * @param pgpKey - * the pgp-key to be validated. Must not be null. - */ - private void validateKey(final PgpKey pgpKey) - { - assertNotNull("pgpKey", pgpKey); - logger.debug("validateKey: {}", pgpKey); - - final Config config = Config.getInstance(); - final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); - - final boolean expired = isExpired(pgpKey.getPublicKey()); - // final boolean disabled = isDisabled(pgpKey.getPublicKey()); - final boolean revoked = pgpKey.getPublicKey().hasRevocation(); - - for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) - { - final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); - - pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 - pgpUserIdTrust.setUltimateCount(0); - pgpUserIdTrust.setFullCount(0); - pgpUserIdTrust.setMarginalCount(0); - - if (expired) - continue; - - // if (disabled) - // continue; - - if (revoked) - continue; - - for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) - { - // It seems, the PGP trust model does not care about the certification level :-( - // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - - // there is no difference (at least according to my tests). - if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION - && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION - && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) - continue; - - final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); - if (signingKey == null) - continue; - - final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); - if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) - && signingOwnerTrust != OwnerTrust.ULTIMATE) - { - // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. - continue; - } - - int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; - if (signingValidity <= TRUST_MARGINAL) - { - // If the signingKey is trusted only marginally or less, we ignore the certification completely. - // Only fully trusted keys are taken into account for transitive trust. - continue; - } - - // The owner-trust of the signing key is relevant. - switch (signingOwnerTrust) - { - case ULTIMATE: - pgpUserIdTrust.incUltimateCount(); - break; - case FULL: - pgpUserIdTrust.incFullCount(); - break; - case MARGINAL: - pgpUserIdTrust.incMarginalCount(); - break; - default: // ignoring! - break; - } - } - - if (pgpUserIdTrust.getUltimateCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_FULL); - else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULL); - else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) - pgpUserIdTrust.setValidity(TRUST_FULL); - else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) - pgpUserIdTrust.setValidity(TRUST_MARGINAL); - } - } -} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java index a0dc6db6a2..beb7268155 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java @@ -4,8 +4,6 @@ /** * Exception thrown by {@link TrustDbIo} when reading from or writing to the trust database failed. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class TrustDbIoException extends TrustDbException { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java index c04a6a1417..5749d76f15 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustModel.java @@ -6,8 +6,6 @@ * Trust-model specifying the policy and algorithm of trust/validity calculations. *

    * OpenPGP/GnuPG supports multiple trust models. This implementation, however, currently supports {@link #PGP} only. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public enum TrustModel { @@ -20,7 +18,7 @@ public enum TrustModel private final int numericId; private final String stringId; - private static TrustModel[] numericId2TrustModel; + private static volatile TrustModel[] numericId2TrustModel; private TrustModel(int numericId, String stringId) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java index ee5e4667c5..5d1db21445 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/Validity.java @@ -3,14 +3,14 @@ import java.util.HashMap; import java.util.Map; +import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; + /** * Validity of a key or user-identity/-attribute. *

    * The validity is calculated by {@link TrustDbImpl#updateTrustDb()} and can be queried by its * {@link TrustDbImpl#getValidity(org.bouncycastle.openpgp.wot.key.PgpKey) getValidity(PgpKey)} or another overloaded * {@code getValidity(...)} method. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public enum Validity { @@ -60,7 +60,7 @@ public enum Validity private final int numericValue; - private static Map numericValue2Validity; + private static volatile Map numericValue2Validity; private Validity(final int numericValue) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Mutex.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Mutex.java new file mode 100644 index 0000000000..47d321e0e2 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Mutex.java @@ -0,0 +1,56 @@ +package org.bouncycastle.openpgp.wot.internal; + +import static org.bouncycastle.openpgp.wot.internal.Util.*; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +/** + * Globally used mutex. + *

    + * There is one mutex instance per pgp/gnupg directory. All classes of the WOT use this same mutex. This strategy + * prevents dead-locks and at the same time allows for maximum concurrency when working with multiple webs-of-trust. + */ +public final class Mutex +{ + private static final WeakHashMap> pgpDir2MutexRef = new WeakHashMap>(); + + private final String pgpDir; + + private Mutex(final String pgpDir) + { + this.pgpDir = assertNotNull("pgpDir", pgpDir); + } + + public static synchronized Mutex forPubringFile(final File pubringFile) { + assertNotNull("pubringFile", pubringFile); + return forPgpDir(pubringFile.getParentFile()); + } + + public static synchronized Mutex forPgpDir(final File dir) { + assertNotNull("dir", dir); + final String pgpDir; + try + { + pgpDir = dir.getCanonicalPath(); + } catch (IOException e) + { + throw new RuntimeException(e); + } + final WeakReference ref = pgpDir2MutexRef.get(pgpDir); + Mutex mutex = ref == null ? null : ref.get(); + if (mutex == null) { + mutex = new Mutex(pgpDir); + pgpDir2MutexRef.put(pgpDir, new WeakReference(mutex)); + } + return mutex; + } + + @Override + public String toString() + { + return String.format("Mutex['%s']", pgpDir); + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java index c0eb534af7..3f89968e05 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpKeyTrust.java @@ -12,7 +12,7 @@ import org.bouncycastle.openpgp.wot.key.PgpUserId; import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; -public class PgpKeyTrust +class PgpKeyTrust { private final PgpKey pgpKey; private final Map nameHash2UserIdTrust = new HashMap<>(); diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java index a63edd0dd6..2ff656cad0 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/PgpUserIdTrust.java @@ -4,7 +4,7 @@ import org.bouncycastle.openpgp.wot.key.PgpUserId; -public class PgpUserIdTrust +class PgpUserIdTrust { private final PgpKeyTrust pgpKeyTrust; private final PgpUserId pgpUserId; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbImpl.java new file mode 100644 index 0000000000..139e4d0181 --- /dev/null +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbImpl.java @@ -0,0 +1,865 @@ +package org.bouncycastle.openpgp.wot.internal; + +import static org.bouncycastle.openpgp.wot.internal.Util.*; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.wot.Config; +import org.bouncycastle.openpgp.wot.OwnerTrust; +import org.bouncycastle.openpgp.wot.TrustConst; +import org.bouncycastle.openpgp.wot.TrustDb; +import org.bouncycastle.openpgp.wot.TrustModel; +import org.bouncycastle.openpgp.wot.Validity; +import org.bouncycastle.openpgp.wot.key.PgpKey; +import org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint; +import org.bouncycastle.openpgp.wot.key.PgpKeyId; +import org.bouncycastle.openpgp.wot.key.PgpKeyRegistry; +import org.bouncycastle.openpgp.wot.key.PgpUserId; +import org.bouncycastle.openpgp.wot.key.PgpUserIdNameHash; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * API implementation for working with GnuPG's {@code trustdb.gpg}. + *

    + * This class was mostly ported from the GnuPG's {@code trustdb.h} and {@code trustdb.c} files. + */ +public class TrustDbImpl implements TrustDb, TrustConst +{ + private static final Logger logger = LoggerFactory.getLogger(TrustDbImpl.class); + + private final PgpKeyRegistry pgpKeyRegistry; + private final Mutex mutex; + private final TrustDbIo trustDbIo; + + private long startTime; + private long nextExpire; + private Map fingerprint2PgpKeyTrust; + private Set klist; + private Set fullTrust; + private DateFormat dateFormatIso8601WithTime; + + /** + * Create a {@code TrustDbImpl} instance with the given {@code trustdb.gpg} file and the given key-registry. + *

    + * Important: You must {@linkplain #close() close} this instance! + *

    + * Important: It is highly recommended not to invoke this constructor directly! Use + * {@link TrustDb.Helper#createInstance(File, PgpKeyRegistry)} instead! + * + * @param file + * the trust-database-file ({@code trustdb.gpg}). Must not be null. + * @param pgpKeyRegistry + * the key-registry. Must not be null. + * @see TrustDb.Helper#createInstance(File, PgpKeyRegistry) + */ + public TrustDbImpl(final File file, final PgpKeyRegistry pgpKeyRegistry) + { + assertNotNull("file", file); + this.pgpKeyRegistry = assertNotNull("pgpKeyRegistry", pgpKeyRegistry); + this.mutex = Mutex.forPubringFile(pgpKeyRegistry.getPubringFile()); + this.trustDbIo = new TrustDbIo(file, mutex); + } + + @Override + public void close() + { + synchronized (mutex) { + trustDbIo.close(); + } + } + + public DateFormat getDateFormatIso8601WithTime() + { + synchronized (mutex) { + if (dateFormatIso8601WithTime == null) + dateFormatIso8601WithTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + return dateFormatIso8601WithTime; + } + } + + protected PgpKeyTrust getPgpKeyTrust(final PgpKey pgpKey) + { + synchronized (mutex) { + PgpKeyTrust pgpKeyTrust = fingerprint2PgpKeyTrust.get(pgpKey.getPgpKeyFingerprint()); + if (pgpKeyTrust == null) + { + pgpKeyTrust = new PgpKeyTrust(pgpKey); + fingerprint2PgpKeyTrust.put(pgpKeyTrust.getPgpKeyFingerprint(), pgpKeyTrust); + } + return pgpKeyTrust; + } + } + + // reset_trust_records(void) + protected void resetTrustRecords() + { + synchronized (mutex) { + TrustRecord record; + long recordNum = 0; + int count = 0, nreset = 0; + + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) + { + if (record.getType() == TrustRecordType.TRUST) + { + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + ++count; + if (trust.getMinOwnerTrust() != 0) + { + trust.setMinOwnerTrust((short) 0); + trustDbIo.putTrustRecord(record); + } + } + else if (record.getType() == TrustRecordType.VALID) + { + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + if (((valid.getValidity() & TRUST_MASK) != 0) + || valid.getMarginalCount() != 0 + || valid.getFullCount() != 0) + { + + valid.setValidity((short) (valid.getValidity() & (~TRUST_MASK))); + valid.setMarginalCount((short) 0); + valid.setFullCount((short) 0); + nreset++; + trustDbIo.putTrustRecord(record); + } + } + } + + logger.debug("resetTrustRecords: {} keys processed ({} validity counts cleared)", count, nreset); + } + } + + @Override + public OwnerTrust getOwnerTrust(PgpKey pgpKey) + { + synchronized (mutex) { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return getOwnerTrust(pgpKey.getPublicKey()); + } + } + + @Override + public void setOwnerTrust(PgpKey pgpKey, final OwnerTrust ownerTrust) + { + synchronized (mutex) { + assertNotNull("pgpKey", pgpKey); + assertNotNull("ownerTrust", ownerTrust); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setOwnerTrust(pgpKey.getPublicKey(), ownerTrust); + } + } + + @Override + public OwnerTrust getOwnerTrust(final PGPPublicKey publicKey) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + // if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) + // return TRUST_UNKNOWN; // TODO maybe we should support other trust models... + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return null; + + return OwnerTrust.fromNumericValue(trust.getOwnerTrust() & TRUST_MASK); + } + } + + @Override + public void setOwnerTrust(final PGPPublicKey publicKey, final OwnerTrust ownerTrust) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + assertNotNull("ownerTrust", ownerTrust); + + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrustAdditionalFlags = trust.getOwnerTrust() & ~TRUST_MASK; + + trust.setOwnerTrust((short) (ownerTrust.getNumericValue() | ownerTrustAdditionalFlags)); + trustDbIo.putTrustRecord(trust); + + markTrustDbStale(); + trustDbIo.flush(); + } + } + + protected TrustRecord.Trust getTrustByPublicKey(PGPPublicKey publicKey) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(publicKey); + return trust; + } + } + + @Override + @Deprecated + public int getValidityRaw(final PGPPublicKey publicKey) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + return _getValidity(publicKey, (PgpUserIdNameHash) null, true); + } + } + + @Override + @Deprecated + public int getValidityRaw(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + return _getValidity(publicKey, pgpUserIdNameHash, true); + } + } + + @Override + public Validity getValidity(final PgpKey pgpKey) + { + synchronized (mutex) { + assertNotNull("pgpKey", pgpKey); + return getValidity(pgpKey.getPublicKey()); + } + } + + @Override + public Validity getValidity(final PgpUserId pgpUserId) + { + synchronized (mutex) { + assertNotNull("pgpUserId", pgpUserId); + return getValidity(pgpUserId.getPgpKey().getPublicKey(), pgpUserId.getNameHash()); + } + } + + @Override + public Validity getValidity(final PGPPublicKey publicKey) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + final int numericValue = _getValidity(publicKey, (PgpUserIdNameHash) null, false); + return Validity.fromNumericValue(numericValue); + } + } + + @Override + public Validity getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + assertNotNull("pgpUserIdNameHash", pgpUserIdNameHash); + final int numericValue = _getValidity(publicKey, pgpUserIdNameHash, false); + return Validity.fromNumericValue(numericValue); + } + } + + /** + * Ported from + * {@code unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk)} + */ + protected int _getValidity(final PGPPublicKey publicKey, final PgpUserIdNameHash pgpUserIdNameHash, + final boolean withFlags) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = getTrustByPublicKey(publicKey); + if (trust == null) + return TRUST_UNKNOWN; + + // Loop over all user IDs + long recordNum = trust.getValidList(); + int validity = 0; + int flags = 0; + // Currently, neither this class nor GnuPG stores any flags in the valid-records, but we're robust + // and thus expect validateKey(...) to maybe put flags into the validity DB, later. Therefore, + // we track them here separately (additive for all sub-keys if no user-id-name-hash is given). + while (recordNum != 0) + { + TrustRecord.Valid valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + assertNotNull("valid", valid); + + if (pgpUserIdNameHash != null) + { + // If a user ID is given we return the validity for that + // user ID ONLY. If the namehash is not found, then there + // is no validity at all (i.e. the user ID wasn't signed). + if (pgpUserIdNameHash.equals(valid.getNameHash())) + { + validity = valid.getValidity() & TRUST_MASK; + flags = valid.getValidity() & ~TRUST_MASK; + break; + } + } + else + { + // If no user ID is given, we take the maximum validity over all user IDs + validity = Math.max(validity, valid.getValidity() & TRUST_MASK); + flags |= valid.getValidity() & ~TRUST_MASK; + } + recordNum = valid.getNext(); + } + + if (withFlags) + { + validity |= flags; + + if ((trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0) + validity |= TRUST_FLAG_DISABLED; + + if (publicKey.hasRevocation()) + validity |= TRUST_FLAG_REVOKED; + + if (isTrustDbStale()) + validity |= TRUST_FLAG_PENDING_CHECK; + } + return validity; + } + } + + // static void update_validity (PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) + protected void updateValidity(PgpUserId pgpUserId, int depth, int validity, int fullCount, int marginalCount) + { + synchronized (mutex) { + assertNotNull("pgpUserId", pgpUserId); + assertNonNegativeShort("depth", depth); + assertNonNegativeShort("validity", validity); + assertNonNegativeShort("fullCount", fullCount); + assertNonNegativeShort("marginalCount", marginalCount); + + TrustRecord.Trust trust = getTrustByPublicKey(pgpUserId.getPgpKey().getPublicKey()); + if (trust == null) + { + // No record yet - create a new one. + trust = new TrustRecord.Trust(); + trust.setFingerprint(pgpUserId.getPgpKey().getPgpKeyFingerprint().getBytes()); + trustDbIo.putTrustRecord(trust); + } + + TrustRecord.Valid valid = null; + + // locate an existing Valid record + final byte[] pgpUserIdNameHashBytes = pgpUserId.getNameHash().getBytes(); + long recordNum = trust.getValidList(); + while (recordNum != 0) + { + valid = trustDbIo.getTrustRecord(recordNum, TrustRecord.Valid.class); + if (Arrays.equals(valid.getNameHash(), pgpUserIdNameHashBytes)) + break; + + recordNum = valid.getNext(); + } + + if (recordNum == 0) + { // insert a new validity record + valid = new TrustRecord.Valid(); + valid.setNameHash(pgpUserIdNameHashBytes); + valid.setNext(trust.getValidList()); + trustDbIo.putTrustRecord(valid); // assigns the recordNum of the new record + trust.setValidList(valid.getRecordNum()); + } + + valid.setValidity((short) validity); + valid.setFullCount((short) fullCount); + valid.setMarginalCount((short) marginalCount); + trust.setDepth((short) depth); + trustDbIo.putTrustRecord(trust); + trustDbIo.putTrustRecord(valid); + } + } + + private static void assertNonNegativeShort(final String name, final int value) + { + assertNotNull("name", name); + + if (value < 0) + throw new IllegalArgumentException(name + " < 0"); + + if (value > Short.MAX_VALUE) + throw new IllegalArgumentException(name + " > Short.MAX_VALUE"); + } + + @Override + public void updateUltimatelyTrustedKeysFromAvailableSecretKeys(boolean onlyIfMissing) + { + synchronized (mutex) { + for (final PgpKey masterKey : pgpKeyRegistry.getMasterKeys()) + { + if (masterKey.getSecretKey() == null) + continue; + + TrustRecord.Trust trust = trustDbIo.getTrustByPublicKey(masterKey.getPublicKey()); + if (trust == null + || trust.getOwnerTrust() == TRUST_UNKNOWN + || !onlyIfMissing) + { + + if (trust == null) + { + trust = new TrustRecord.Trust(); + trust.setFingerprint(masterKey.getPgpKeyFingerprint().getBytes()); + } + + trust.setDepth((short) 0); + trust.setOwnerTrust((short) TRUST_ULTIMATE); + trustDbIo.putTrustRecord(trust); + } + } + } + } + + protected Set getUltimatelyTrustedKeyFingerprints() + { + synchronized (mutex) { + Set result = new HashSet(); + TrustRecord record; + long recordNum = 0; + while ((record = trustDbIo.getTrustRecord(++recordNum)) != null) + { + if (record.getType() == TrustRecordType.TRUST) + { + TrustRecord.Trust trust = (TrustRecord.Trust) record; + if ((trust.getOwnerTrust() & TRUST_MASK) == TRUST_ULTIMATE) + result.add(new PgpKeyFingerprint(trust.getFingerprint())); + } + } + return result; + } + } + + @Override + public boolean isExpired(PGPPublicKey publicKey) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + + final Date creationTime = publicKey.getCreationTime(); + + final long validSeconds = publicKey.getValidSeconds(); + if (validSeconds != 0) + { + long validUntilTimestamp = creationTime.getTime() + (validSeconds * 1000); + return validUntilTimestamp < System.currentTimeMillis(); + } + return false; + // TODO there seem to be keys (very old keys) that seem to encode the validity differently. + // For example, the real key 86A331B667F0D02F is expired according to my gpg, but it + // is not expired according to this code :-( I experimented with checking the userIds, but to no avail. + // It's a very small number of keys only, hence I ignore it for now ;-) + } + } + + @Override + public boolean isDisabled(PgpKey pgpKey) + { + synchronized (mutex) { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + return isDisabled(pgpKey.getPublicKey()); + } + } + + @Override + public void setDisabled(PgpKey pgpKey, final boolean disabled) + { + synchronized (mutex) { + assertNotNull("pgpKey", pgpKey); + if (pgpKey.getMasterKey() != null) + pgpKey = pgpKey.getMasterKey(); + + setDisabled(pgpKey.getPublicKey(), disabled); + } + } + + @Override + public boolean isDisabled(final PGPPublicKey publicKey) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + return false; + + return (trust.getOwnerTrust() & TRUST_FLAG_DISABLED) != 0; + } + } + + @Override + public void setDisabled(final PGPPublicKey publicKey, final boolean disabled) + { + synchronized (mutex) { + assertNotNull("publicKey", publicKey); + TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(publicKey.getFingerprint()); + if (trust == null) + { + trust = new TrustRecord.Trust(); + trust.setFingerprint(publicKey.getFingerprint()); + } + + int ownerTrust = trust.getOwnerTrust(); + if (disabled) + ownerTrust = ownerTrust | TRUST_FLAG_DISABLED; + else + ownerTrust = ownerTrust & ~TRUST_FLAG_DISABLED; + + trust.setOwnerTrust((short) ownerTrust); + + trustDbIo.putTrustRecord(trust); + trustDbIo.flush(); + } + } + + @Override + public boolean isTrustDbStale() + { + synchronized (mutex) { + final Config config = Config.getInstance(); + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + + if (config.getTrustModel() != version.getTrustModel()) + { + TrustModel configTrustModel; + try + { + configTrustModel = TrustModel.fromNumericId(config.getTrustModel()); + } catch (IllegalArgumentException x) + { + configTrustModel = null; + } + + TrustModel versionTrustModel; + try + { + versionTrustModel = TrustModel.fromNumericId(version.getTrustModel()); + } catch (IllegalArgumentException x) + { + versionTrustModel = null; + } + + logger.debug("isTrustDbStale: stale=true config.trustModel={} ({}) trustDb.trustModel={} ({})", + config.getTrustModel(), configTrustModel, version.getTrustModel(), versionTrustModel); + + return true; + } + + if (config.getCompletesNeeded() != version.getCompletesNeeded()) + { + logger.debug("isTrustDbStale: stale=true config.completesNeeded={} trustDb.completesNeeded={}", + config.getCompletesNeeded(), version.getCompletesNeeded()); + + return true; + } + + if (config.getMarginalsNeeded() != version.getMarginalsNeeded()) + { + logger.debug("isTrustDbStale: stale=true config.marginalsNeeded={} trustDb.marginalsNeeded={}", + config.getMarginalsNeeded(), version.getMarginalsNeeded()); + + return true; + } + + if (config.getMaxCertDepth() != version.getCertDepth()) + { + logger.debug("isTrustDbStale: stale=true config.maxCertDepth={} trustDb.maxCertDepth={}", + config.getMaxCertDepth(), version.getCertDepth()); + + return true; + } + + final Date now = new Date(); + if (version.getNextCheck().before(now)) + { + logger.debug("isTrustDbStale: stale=true nextCheck={} now={}", + getDateFormatIso8601WithTime().format(version.getNextCheck()), + getDateFormatIso8601WithTime().format(now)); + + return true; + } + + logger.trace("isTrustDbStale: stale=false"); + return false; + } + } + + @Override + public void markTrustDbStale() + { + synchronized (mutex) { + final TrustRecord.Version version = trustDbIo.getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + version.setNextCheck(new Date(0)); + trustDbIo.putTrustRecord(version); + } + } + + @Override + public void updateTrustDbIfNeeded() + { + synchronized (mutex) { + if (isTrustDbStale()) + updateTrustDb(); + } + } + + /** + * {@inheritDoc} + *

    + * Inspired by {@code static int validate_keys (int interactive)}. This function was not ported, because the + * implementation looked overly complicated. This method here is a re-implementation from scratch. It still seems to + * come very closely to the behaviour of GnuPG's original code. + */ + @Override + public void updateTrustDb() + { + synchronized (mutex) { + final Config config = Config.getInstance(); + try + { + fingerprint2PgpKeyTrust = new HashMap<>(); + fullTrust = new HashSet<>(); + + startTime = System.currentTimeMillis() / 1000; + nextExpire = Long.MAX_VALUE; + + resetTrustRecords(); + + final Set ultimatelyTrustedKeyFingerprints = getUltimatelyTrustedKeyFingerprints(); + if (ultimatelyTrustedKeyFingerprints.isEmpty()) + { + logger.warn("updateTrustDb: There are no ultimately trusted keys!"); + return; + } + + // mark all UTKs as used and fully_trusted and set validity to ultimate + for (final PgpKeyFingerprint utkFpr : ultimatelyTrustedKeyFingerprints) + { + final PgpKey utk = pgpKeyRegistry.getPgpKey(utkFpr); + if (utk == null) + { + logger.warn("public key of ultimately trusted key '{}' not found!", utkFpr.toHumanString()); + continue; + } + + fullTrust.add(utkFpr); + + for (PgpUserId pgpUserId : utk.getPgpUserIds()) + updateValidity(pgpUserId, 0, TRUST_ULTIMATE, 0, 0); + + final long expireDate = getExpireTimestamp(utk.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + klist = ultimatelyTrustedKeyFingerprints; + + for (int depth = 0; depth < config.getMaxCertDepth(); ++depth) + { + final List validatedKeys = validateKeyList(); + + klist = new HashSet<>(); + for (PgpKey pgpKey : validatedKeys) + { + PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + klist.add(pgpKey.getPgpKeyFingerprint()); + + for (final PgpUserIdTrust pgpUserIdTrust : pgpKeyTrust.getPgpUserIdTrusts()) + { + final PgpUserId pgpUserId = pgpUserIdTrust.getPgpUserId(); + + final int validity = pgpUserIdTrust.getValidity(); + updateValidity(pgpUserId, depth, validity, + pgpUserIdTrust.getFullCount(), pgpUserIdTrust.getMarginalCount()); + + if (validity >= TRUST_FULL) + fullTrust.add(pgpUserIdTrust.getPgpUserId().getPgpKey().getPgpKeyFingerprint()); + } + + final long expireDate = getExpireTimestamp(pgpKey.getPublicKey()); + if (expireDate >= startTime && expireDate < nextExpire) + nextExpire = expireDate; + } + + logger.debug("updateTrustDb: depth={} keys={}", + depth, validatedKeys.size()); + } + + final Date nextExpireDate = new Date(nextExpire * 1000); + trustDbIo.updateVersionRecord(nextExpireDate); + + trustDbIo.flush(); + + logger.info("updateTrustDb: Next trust-db expiration date: {}", + getDateFormatIso8601WithTime().format(nextExpireDate)); + } finally + { + fingerprint2PgpKeyTrust = null; + klist = null; + fullTrust = null; + } + } + } + + private long getExpireTimestamp(PGPPublicKey pk) + { + final long validSeconds = pk.getValidSeconds(); + if (validSeconds == 0) + return Long.MAX_VALUE; + + final long result = (pk.getCreationTime().getTime() / 1000) + validSeconds; + return result; + } + + /** + * Inspired by {@code static struct key_array *validate_key_list (KEYDB_HANDLE hd, KeyHashTable full_trust, + * struct key_item *klist, u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see + * {@link #updateTrustDb()}. + * + * @return the keys that were processed by this method. + */ + private List validateKeyList() + { + final List result = new ArrayList<>(); + final Set signedPgpKeyFingerprints = new HashSet<>(); + for (PgpKeyFingerprint signingPgpKeyFingerprint : klist) + signedPgpKeyFingerprints.addAll(pgpKeyRegistry.getPgpKeyFingerprintsSignedBy(signingPgpKeyFingerprint)); + + signedPgpKeyFingerprints.removeAll(fullTrust); // no need to validate those that are already fully trusted + + for (final PgpKeyFingerprint pgpKeyFingerprint : signedPgpKeyFingerprints) + { + final PgpKey pgpKey = pgpKeyRegistry.getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + { + logger.warn("key disappeared: fingerprint='{}'", pgpKeyFingerprint); + continue; + } + result.add(pgpKey); + validateKey(pgpKey); + } + return result; + } + + /** + * Inspired by {@code static int validate_one_keyblock (KBNODE kb, struct key_item *klist, + * u32 curtime, u32 *next_expire)}, but re-implemented from scratch - see {@link #updateTrustDb()}. + * + * @param pgpKey + * the pgp-key to be validated. Must not be null. + */ + private void validateKey(final PgpKey pgpKey) + { + assertNotNull("pgpKey", pgpKey); + logger.debug("validateKey: {}", pgpKey); + + final Config config = Config.getInstance(); + final PgpKeyTrust pgpKeyTrust = getPgpKeyTrust(pgpKey); + + final boolean expired = isExpired(pgpKey.getPublicKey()); + // final boolean disabled = isDisabled(pgpKey.getPublicKey()); + final boolean revoked = pgpKey.getPublicKey().hasRevocation(); + + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) + { + final PgpUserIdTrust pgpUserIdTrust = pgpKeyTrust.getPgpUserIdTrust(pgpUserId); + + pgpUserIdTrust.setValidity(0); // TRUST_UNKNOWN = 0 + pgpUserIdTrust.setUltimateCount(0); + pgpUserIdTrust.setFullCount(0); + pgpUserIdTrust.setMarginalCount(0); + + if (expired) + continue; + + // if (disabled) + // continue; + + if (revoked) + continue; + + for (PGPSignature certification : pgpKeyRegistry.getSignatures(pgpUserId)) + { + // It seems, the PGP trust model does not care about the certification level :-( + // Any of the 3 DEFAULT, CASUAL, POSITIVE is as fine as the other - + // there is no difference (at least according to my tests). + if (certification.getSignatureType() != PGPSignature.DEFAULT_CERTIFICATION + && certification.getSignatureType() != PGPSignature.CASUAL_CERTIFICATION + && certification.getSignatureType() != PGPSignature.POSITIVE_CERTIFICATION) + continue; + + final PgpKey signingKey = pgpKeyRegistry.getPgpKey(new PgpKeyId(certification.getKeyID())); + if (signingKey == null) + continue; + + final OwnerTrust signingOwnerTrust = getOwnerTrust(signingKey.getPublicKey()); + if (signingKey.getPgpKeyId().equals(pgpKey.getPgpKeyId()) + && signingOwnerTrust != OwnerTrust.ULTIMATE) + { + // It's *not* our own key [*not* ULTIMATE] - hence we ignore the self-signature. + continue; + } + + int signingValidity = getValidityRaw(signingKey.getPublicKey()) & TRUST_MASK; + if (signingValidity <= TRUST_MARGINAL) + { + // If the signingKey is trusted only marginally or less, we ignore the certification completely. + // Only fully trusted keys are taken into account for transitive trust. + continue; + } + + // The owner-trust of the signing key is relevant. + switch (signingOwnerTrust) + { + case ULTIMATE: + pgpUserIdTrust.incUltimateCount(); + break; + case FULL: + pgpUserIdTrust.incFullCount(); + break; + case MARGINAL: + pgpUserIdTrust.incMarginalCount(); + break; + default: // ignoring! + break; + } + } + + if (pgpUserIdTrust.getUltimateCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_FULL); + else if (pgpUserIdTrust.getFullCount() >= config.getCompletesNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULL); + else if (pgpUserIdTrust.getFullCount() + pgpUserIdTrust.getMarginalCount() >= config.getMarginalsNeeded()) + pgpUserIdTrust.setValidity(TRUST_FULL); + else if (pgpUserIdTrust.getFullCount() >= 1 || pgpUserIdTrust.getMarginalCount() >= 1) + pgpUserIdTrust.setValidity(TRUST_MARGINAL); + } + } +} diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java index 510728ebbb..8e266ca4eb 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; import java.util.Arrays; import java.util.Date; import java.util.HashMap; @@ -18,7 +19,6 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.wot.Config; import org.bouncycastle.openpgp.wot.TrustConst; -import org.bouncycastle.openpgp.wot.TrustDbImpl; import org.bouncycastle.openpgp.wot.TrustDbIoException; import org.bouncycastle.openpgp.wot.internal.TrustRecord.HashLst; import org.slf4j.Logger; @@ -35,8 +35,6 @@ * the {@link TrustDbImpl}. *

    * This class was mostly ported from the GnuPG's {@code tdbio.h} and {@code tdbio.c} files. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class TrustDbIo implements AutoCloseable, TrustConst { @@ -47,6 +45,7 @@ public class TrustDbIo implements AutoCloseable, TrustConst private final Map cacheRecordNum2TrustRecord = new HashMap<>(); private final File file; + private final Mutex mutex; private final RandomAccessFile raf; private final FileLock fileLock; private boolean closed; @@ -58,21 +57,69 @@ public class TrustDbIo implements AutoCloseable, TrustConst * * @param file * the file to read from and write to. Must not be null. Is created, if not yet existing. + * @param mutex the mutex used for the pgp/gnupg directory the given {@code trustdb.gpg} belongs to. Must not be null. * @throws TrustDbIoException * if reading from/writing to the {@code trustdb.gpg} failed. */ - public TrustDbIo(final File file) throws TrustDbIoException + public TrustDbIo(final File file, final Mutex mutex) throws TrustDbIoException { this.file = assertNotNull("file", file); + this.mutex = assertNotNull("mutex", mutex); + RandomAccessFile raf = null; + FileLock fileLock = null; try { - this.raf = new RandomAccessFile(file, "rw"); // or better use rwd/rws? maybe manually calling sync is - // sufficient?! - fileLock = raf.getChannel().lock(); - } catch (IOException x) - { + raf = new RandomAccessFile(file, "rw"); + + // Try to lock the file for 60 seconds - using tryLock() instead of lock(), because I ran + // into exceptions already, even though lock() should wait according to javadoc. + final int timeoutMillis = 60 * 1000; + final int sleepMillis = 500; + final int tryCount = timeoutMillis / sleepMillis; + for (int i = 0; i < tryCount; ++i) + { + if (fileLock == null && i != 0) { + logger.warn("Locking file '{}' failed. Retrying.", file.getAbsolutePath()); + try + { + Thread.sleep(sleepMillis); + } catch (InterruptedException e) + { + doNothing(); // ignore + } + } + + try + { + fileLock = raf.getChannel().tryLock(); + } catch (OverlappingFileLockException y) + { + doNothing(); // ignore (it's quite strange that *try*Lock() might still throw this exception at all) + } + if (fileLock != null) + break; + } + + if (fileLock == null) + fileLock = raf.getChannel().lock(); + + } catch (IOException x) { throw new TrustDbIoException(x); + } finally { + // If opening the file succeeded, but locking it failed, we must close the RandomAccessFile now. + if (fileLock == null && raf != null) { + try { + // We only come here, if there's currently an exception flying. Hence, we close the file + // inside this new try-catch-block in order to prevent the primary exception from being + // lost. A new exception otherwise would suppress the primary exception. + raf.close(); + } catch (Exception e) { + logger.warn("Closing file failed: " + e, e); + } + } } + this.raf = raf; + this.fileLock = fileLock; if (getTrustRecord(0, TrustRecord.Version.class) == null) createVersionRecord(); @@ -98,55 +145,63 @@ private void createVersionRecord() throws TrustDbIoException flush(); } - public synchronized void updateVersionRecord(final Date nextCheck) throws TrustDbIoException + public void updateVersionRecord(final Date nextCheck) throws TrustDbIoException { - assertNotNull("nextCheck", nextCheck); + synchronized (mutex) { + assertNotNull("nextCheck", nextCheck); - TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); - Config config = Config.getInstance(); + Config config = Config.getInstance(); - version.setCreated(new Date()); - version.setNextCheck(nextCheck); - version.setMarginalsNeeded(config.getMarginalsNeeded()); - version.setCompletesNeeded(config.getCompletesNeeded()); - version.setCertDepth(config.getMaxCertDepth()); - version.setTrustModel(config.getTrustModel()); - version.setMinCertLevel(config.getMinCertLevel()); + version.setCreated(new Date()); + version.setNextCheck(nextCheck); + version.setMarginalsNeeded(config.getMarginalsNeeded()); + version.setCompletesNeeded(config.getCompletesNeeded()); + version.setCertDepth(config.getMaxCertDepth()); + version.setTrustModel(config.getTrustModel()); + version.setMinCertLevel(config.getMinCertLevel()); - putTrustRecord(version); + putTrustRecord(version); + } } public TrustRecord getTrustRecord(final long recordNum) throws TrustDbIoException { - return getTrustRecord(recordNum, TrustRecord.class); + synchronized (mutex) { + return getTrustRecord(recordNum, TrustRecord.class); + } } public TrustRecord.Trust getTrustByPublicKey(PGPPublicKey pk) throws TrustDbIoException { - final byte[] fingerprint = pk.getFingerprint(); - return getTrustByFingerprint(fingerprint); + synchronized (mutex) { + final byte[] fingerprint = pk.getFingerprint(); + return getTrustByFingerprint(fingerprint); + } } /** Record number of the trust hashtable. */ private long trustHashRec = 0; - protected synchronized long getTrustHashRec() + protected long getTrustHashRec() { - if (trustHashRec == 0) - { - TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); - - trustHashRec = version.getTrustHashTbl(); + synchronized (mutex) { if (trustHashRec == 0) { - createHashTable(0); + TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); + trustHashRec = version.getTrustHashTbl(); + if (trustHashRec == 0) + { + createHashTable(0); + trustHashRec = version.getTrustHashTbl(); + } } + return trustHashRec; } - return trustHashRec; } /** @@ -194,77 +249,81 @@ private void createHashTable(int type) throws TrustDbIoException } // ulong tdbio_new_recnum () - protected synchronized long newRecordNum() throws TrustDbIoException + protected long newRecordNum() throws TrustDbIoException { - long recordNum; + synchronized (mutex) { + long recordNum; - // Look for Free records. - final TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); - assertNotNull("version", version); + // Look for Free records. + final TrustRecord.Version version = getTrustRecord(0, TrustRecord.Version.class); + assertNotNull("version", version); - if (version.getFirstFree() != 0) - { - recordNum = version.getFirstFree(); - TrustRecord.Free free = getTrustRecord(recordNum, TrustRecord.Free.class); - assertNotNull("free", free); + if (version.getFirstFree() != 0) + { + recordNum = version.getFirstFree(); + TrustRecord.Free free = getTrustRecord(recordNum, TrustRecord.Free.class); + assertNotNull("free", free); - // Update dir record. - version.setFirstFree(free.getNext()); - putTrustRecord(version); + // Update dir record. + version.setFirstFree(free.getNext()); + putTrustRecord(version); - // Zero out the new record. Means we convert from Free to Unused. - // => Done at the end! - } - else - { // Not found - append a new record. - final long fileLength; - try - { - fileLength = raf.length(); - } catch (IOException e) - { - throw new TrustDbIoException(e); + // Zero out the new record. Means we convert from Free to Unused. + // => Done at the end! } - recordNum = fileLength / TRUST_RECORD_LEN; + else + { // Not found - append a new record. + final long fileLength; + try + { + fileLength = raf.length(); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } + recordNum = fileLength / TRUST_RECORD_LEN; - if (recordNum < 1) // this is will never be the first record - throw new IllegalStateException("recnum < 1"); + if (recordNum < 1) // this is will never be the first record + throw new IllegalStateException("recnum < 1"); - // Maybe our file-length is not up-to-date => consult the dirty records. - if (!dirtyRecordNum2TrustRecord.isEmpty()) - { - long lastDirtyRecordNum = dirtyRecordNum2TrustRecord.lastKey(); - if (lastDirtyRecordNum >= recordNum) - recordNum = lastDirtyRecordNum + 1; - } + // Maybe our file-length is not up-to-date => consult the dirty records. + if (!dirtyRecordNum2TrustRecord.isEmpty()) + { + long lastDirtyRecordNum = dirtyRecordNum2TrustRecord.lastKey(); + if (lastDirtyRecordNum >= recordNum) + recordNum = lastDirtyRecordNum + 1; + } - // We must add a record, so that the next call to this function returns another recnum. - // => Done at the end! - } + // We must add a record, so that the next call to this function returns another recnum. + // => Done at the end! + } - final TrustRecord.Unused unused = new TrustRecord.Unused(); - unused.setRecordNum(recordNum); - putTrustRecord(unused); + final TrustRecord.Unused unused = new TrustRecord.Unused(); + unused.setRecordNum(recordNum); + putTrustRecord(unused); - return recordNum; + return recordNum; + } } - public synchronized TrustRecord.Trust getTrustByFingerprint(final byte[] fingerprint) throws TrustDbIoException + public TrustRecord.Trust getTrustByFingerprint(final byte[] fingerprint) throws TrustDbIoException { - /* Locate the trust record using the hash table */ - TrustRecord rec = getTrustRecordViaHashTable(getTrustHashRec(), fingerprint, new TrustRecordMatcher() - { - @Override - public boolean matches(final TrustRecord trustRecord) + synchronized (mutex) { + /* Locate the trust record using the hash table */ + TrustRecord rec = getTrustRecordViaHashTable(getTrustHashRec(), fingerprint, new TrustRecordMatcher() { - if (!(trustRecord instanceof TrustRecord.Trust)) - return false; + @Override + public boolean matches(final TrustRecord trustRecord) + { + if (!(trustRecord instanceof TrustRecord.Trust)) + return false; - final TrustRecord.Trust trust = (TrustRecord.Trust) trustRecord; - return Arrays.equals(trust.getFingerprint(), fingerprint); - } - }); - return (TrustRecord.Trust) rec; + final TrustRecord.Trust trust = (TrustRecord.Trust) trustRecord; + return Arrays.equals(trust.getFingerprint(), fingerprint); + } + }); + return (TrustRecord.Trust) rec; + } } private static interface TrustRecordMatcher @@ -274,331 +333,339 @@ private static interface TrustRecordMatcher // static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, // const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) - public synchronized TrustRecord getTrustRecordViaHashTable(long table, byte[] key, TrustRecordMatcher matcher) + public TrustRecord getTrustRecordViaHashTable(long table, byte[] key, TrustRecordMatcher matcher) { - long hashrec, item; - int msb; - int level = 0; - - hashrec = table; - next_level: while (true) - { - msb = key[level] & 0xff; - hashrec += msb / ITEMS_PER_HTBL_RECORD; - TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); - // assertNotNull("hashTable", hashTable); - if (hashTable == null) - return null; // not found! + synchronized (mutex) { + long hashrec, item; + int msb; + int level = 0; - item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); - if (item == 0) - return null; // not found! + hashrec = table; + next_level: while (true) + { + msb = key[level] & 0xff; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); + // assertNotNull("hashTable", hashTable); + if (hashTable == null) + return null; // not found! - TrustRecord record = getTrustRecord(item); - assertNotNull("record", record); + item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); + if (item == 0) + return null; // not found! - if (record.getType() == TrustRecordType.HTBL) - { - hashrec = item; - if (++level >= key.length) - throw new TrustDbIoException("hashtable has invalid indirections"); + TrustRecord record = getTrustRecord(item); + assertNotNull("record", record); - continue next_level; - } + if (record.getType() == TrustRecordType.HTBL) + { + hashrec = item; + if (++level >= key.length) + throw new TrustDbIoException("hashtable has invalid indirections"); - if (record.getType() == TrustRecordType.HLST) - { - TrustRecord.HashLst hashList = (TrustRecord.HashLst) record; + continue next_level; + } - for (;;) + if (record.getType() == TrustRecordType.HLST) { - for (int i = 0; i < ITEMS_PER_HLST_RECORD; i++) + TrustRecord.HashLst hashList = (TrustRecord.HashLst) record; + + for (;;) { - if (hashList.getRNum(i) != 0) + for (int i = 0; i < ITEMS_PER_HLST_RECORD; i++) { - TrustRecord tmp = getTrustRecord(hashList.getRNum(i)); - if (tmp != null && matcher.matches(tmp)) - return tmp; + if (hashList.getRNum(i) != 0) + { + TrustRecord tmp = getTrustRecord(hashList.getRNum(i)); + if (tmp != null && matcher.matches(tmp)) + return tmp; + } } - } - if (hashList.getNext() != 0) - { - hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); - assertNotNull("hashList", hashList); + if (hashList.getNext() != 0) + { + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + assertNotNull("hashList", hashList); + } + else + return null; } - else - return null; } - } - if (matcher.matches(record)) - return record; - else - return null; + if (matcher.matches(record)) + return record; + else + return null; + } } } - public synchronized T getTrustRecord(final long recordNum, Class expectedTrustRecordClass) + public T getTrustRecord(final long recordNum, Class expectedTrustRecordClass) throws TrustDbIoException { - assertNotNull("expectedTrustRecordClass", expectedTrustRecordClass); - final TrustRecordType expectedType = expectedTrustRecordClass == - TrustRecord.class ? null : TrustRecordType.fromClass(expectedTrustRecordClass); + synchronized (mutex) { + assertNotNull("expectedTrustRecordClass", expectedTrustRecordClass); + final TrustRecordType expectedType = expectedTrustRecordClass == + TrustRecord.class ? null : TrustRecordType.fromClass(expectedTrustRecordClass); - TrustRecord record = getFromCache(recordNum); - if (record == null) - { - try - { - raf.seek(recordNum * TRUST_RECORD_LEN); - } catch (IOException x) + TrustRecord record = getFromCache(recordNum); + if (record == null) { - throw new TrustDbIoException(x); - } + try + { + raf.seek(recordNum * TRUST_RECORD_LEN); + } catch (IOException x) + { + throw new TrustDbIoException(x); + } - final byte[] buf = new byte[TRUST_RECORD_LEN]; - try - { - raf.readFully(buf); - } catch (EOFException x) - { - return null; - } catch (IOException x) + final byte[] buf = new byte[TRUST_RECORD_LEN]; + try + { + raf.readFully(buf); + } catch (EOFException x) + { + return null; + } catch (IOException x) + { + throw new TrustDbIoException(x); + } + + int bufIdx = 0; + + final TrustRecordType type = TrustRecordType.fromId((short) (buf[bufIdx++] & 0xFF)); + if (expectedType != null && !expectedType.equals(type)) + throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, + type)); + + ++bufIdx; // Skip reserved byte. + + switch (type) + { + case UNUSED: // unused (free) record + record = new TrustRecord.Unused(); + break; + case VERSION: // version record + final TrustRecord.Version version = new TrustRecord.Version(); + record = version; + + --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. + if (buf[bufIdx++] != 'g' + || buf[bufIdx++] != 'p' + || buf[bufIdx++] != 'g') + throw new TrustDbIoException(String.format("Not a trustdb file: %s", file.getAbsolutePath())); + + version.version = (short) (buf[bufIdx++] & 0xFF); + version.marginalsNeeded = (short) (buf[bufIdx++] & 0xFF); + version.completesNeeded = (short) (buf[bufIdx++] & 0xFF); + version.certDepth = (short) (buf[bufIdx++] & 0xFF); + version.trustModel = (short) (buf[bufIdx++] & 0xFF); + version.minCertLevel = (short) (buf[bufIdx++] & 0xFF); + + bufIdx += 2; // no idea why, but we have to skip 2 bytes + version.created = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); + bufIdx += 4; + version.nextCheck = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); + bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + bufIdx += 4; // no idea why, but we have to skip 4 bytes + version.firstFree = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + bufIdx += 4; // no idea why, but we have to skip 4 bytes + version.trustHashTbl = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + + if (version.version != 3) + throw new TrustDbIoException(String.format( + "Wrong version number (3 expected, but %d found): %s", version.version, + file.getAbsolutePath())); + break; + case FREE: + final TrustRecord.Free free = new TrustRecord.Free(); + record = free; + free.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + break; + case HTBL: + final TrustRecord.HashTbl hashTbl = new TrustRecord.HashTbl(); + record = hashTbl; + for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) + { + hashTbl.item[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + } + break; + case HLST: + final TrustRecord.HashLst hashLst = new TrustRecord.HashLst(); + record = hashLst; + hashLst.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + { + hashLst.rnum[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + } + break; + case TRUST: + final TrustRecord.Trust trust = new TrustRecord.Trust(); + record = trust; + System.arraycopy(buf, bufIdx, trust.fingerprint, 0, 20); + bufIdx += 20; + trust.ownerTrust = (short) (buf[bufIdx++] & 0xFF); + trust.depth = (short) (buf[bufIdx++] & 0xFF); + trust.minOwnerTrust = (short) (buf[bufIdx++] & 0xFF); + ++bufIdx; // no idea why, but we have to skip 1 byte + trust.validList = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + break; + case VALID: + final TrustRecord.Valid valid = new TrustRecord.Valid(); + record = valid; + System.arraycopy(buf, bufIdx, valid.nameHash, 0, 20); + bufIdx += 20; + valid.validity = (short) (buf[bufIdx++] & 0xFF); + valid.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + bufIdx += 4; + valid.fullCount = (short) (buf[bufIdx++] & 0xFF); + valid.marginalCount = (short) (buf[bufIdx++] & 0xFF); + break; + default: + throw new IllegalArgumentException("Unexpected TrustRecordType: " + type); + } + record.recordNum = recordNum; + putToCache(record); + } + else { - throw new TrustDbIoException(x); + if (expectedType != null && !expectedType.equals(record.getType())) + throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, + record.getType())); } - int bufIdx = 0; + return expectedTrustRecordClass.cast(record); + } + } + + public void putTrustRecord(final TrustRecord trustRecord) throws TrustDbIoException + { + synchronized (mutex) { + assertNotNull("trustRecord", trustRecord); + + if (trustRecord.getRecordNum() < 0) + trustRecord.setRecordNum(newRecordNum()); + + putToCache(trustRecord); - final TrustRecordType type = TrustRecordType.fromId((short) (buf[bufIdx++] & 0xFF)); - if (expectedType != null && !expectedType.equals(type)) - throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, - type)); + final long recordNum = trustRecord.getRecordNum(); + dirtyRecordNum2TrustRecord.put(recordNum, trustRecord); + if (trustRecord instanceof TrustRecord.Trust) + updateHashTable(getTrustHashRec(), ((TrustRecord.Trust) trustRecord).getFingerprint(), recordNum); + } + } + + protected void writeTrustRecord(final TrustRecord record) throws TrustDbIoException + { + synchronized (mutex) { + int bufIdx = 0; + final byte[] buf = new byte[TRUST_RECORD_LEN]; + + buf[bufIdx++] = (byte) record.getType().getId(); ++bufIdx; // Skip reserved byte. - switch (type) + switch (record.getType()) { case UNUSED: // unused (free) record - record = new TrustRecord.Unused(); break; case VERSION: // version record - final TrustRecord.Version version = new TrustRecord.Version(); - record = version; + final TrustRecord.Version version = (TrustRecord.Version) record; --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. - if (buf[bufIdx++] != 'g' - || buf[bufIdx++] != 'p' - || buf[bufIdx++] != 'g') - throw new TrustDbIoException(String.format("Not a trustdb file: %s", file.getAbsolutePath())); - - version.version = (short) (buf[bufIdx++] & 0xFF); - version.marginalsNeeded = (short) (buf[bufIdx++] & 0xFF); - version.completesNeeded = (short) (buf[bufIdx++] & 0xFF); - version.certDepth = (short) (buf[bufIdx++] & 0xFF); - version.trustModel = (short) (buf[bufIdx++] & 0xFF); - version.minCertLevel = (short) (buf[bufIdx++] & 0xFF); + buf[bufIdx++] = 'g'; + buf[bufIdx++] = 'p'; + buf[bufIdx++] = 'g'; + + buf[bufIdx++] = (byte) version.version; + buf[bufIdx++] = (byte) version.marginalsNeeded; + buf[bufIdx++] = (byte) version.completesNeeded; + buf[bufIdx++] = (byte) version.certDepth; + buf[bufIdx++] = (byte) version.trustModel; + buf[bufIdx++] = (byte) version.minCertLevel; bufIdx += 2; // no idea why, but we have to skip 2 bytes - version.created = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); + + intToBytes((int) (version.created.getTime() / 1000L), buf, bufIdx); bufIdx += 4; - version.nextCheck = new Date(1000L * (bytesToInt(buf, bufIdx) & 0xFFFFFFFFL)); + intToBytes((int) (version.nextCheck.getTime() / 1000L), buf, bufIdx); bufIdx += 4; bufIdx += 4; // no idea why, but we have to skip 4 bytes bufIdx += 4; // no idea why, but we have to skip 4 bytes - version.firstFree = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + intToBytes((int) version.firstFree, buf, bufIdx); bufIdx += 4; bufIdx += 4; // no idea why, but we have to skip 4 bytes - version.trustHashTbl = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + intToBytes((int) version.trustHashTbl, buf, bufIdx); bufIdx += 4; if (version.version != 3) - throw new TrustDbIoException(String.format( - "Wrong version number (3 expected, but %d found): %s", version.version, - file.getAbsolutePath())); + throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", + version.version, file.getAbsolutePath())); break; case FREE: - final TrustRecord.Free free = new TrustRecord.Free(); - record = free; - free.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + final TrustRecord.Free free = (TrustRecord.Free) record; + intToBytes((int) free.next, buf, bufIdx); bufIdx += 4; break; case HTBL: - final TrustRecord.HashTbl hashTbl = new TrustRecord.HashTbl(); - record = hashTbl; + final TrustRecord.HashTbl hashTbl = (TrustRecord.HashTbl) record; for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) { - hashTbl.item[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + intToBytes((int) hashTbl.item[i], buf, bufIdx); bufIdx += 4; } break; case HLST: - final TrustRecord.HashLst hashLst = new TrustRecord.HashLst(); - record = hashLst; - hashLst.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + final TrustRecord.HashLst hashLst = (TrustRecord.HashLst) record; + intToBytes((int) hashLst.next, buf, bufIdx); bufIdx += 4; for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { - hashLst.rnum[i] = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + intToBytes((int) hashLst.rnum[i], buf, bufIdx); bufIdx += 4; } break; case TRUST: - final TrustRecord.Trust trust = new TrustRecord.Trust(); - record = trust; - System.arraycopy(buf, bufIdx, trust.fingerprint, 0, 20); + final TrustRecord.Trust trust = (TrustRecord.Trust) record; + System.arraycopy(trust.fingerprint, 0, buf, bufIdx, 20); bufIdx += 20; - trust.ownerTrust = (short) (buf[bufIdx++] & 0xFF); - trust.depth = (short) (buf[bufIdx++] & 0xFF); - trust.minOwnerTrust = (short) (buf[bufIdx++] & 0xFF); + buf[bufIdx++] = (byte) trust.ownerTrust; + buf[bufIdx++] = (byte) trust.depth; + buf[bufIdx++] = (byte) trust.minOwnerTrust; ++bufIdx; // no idea why, but we have to skip 1 byte - trust.validList = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + intToBytes((int) trust.validList, buf, bufIdx); bufIdx += 4; break; case VALID: - final TrustRecord.Valid valid = new TrustRecord.Valid(); - record = valid; - System.arraycopy(buf, bufIdx, valid.nameHash, 0, 20); + final TrustRecord.Valid valid = (TrustRecord.Valid) record; + System.arraycopy(valid.nameHash, 0, buf, bufIdx, 20); bufIdx += 20; - valid.validity = (short) (buf[bufIdx++] & 0xFF); - valid.next = bytesToInt(buf, bufIdx) & 0xFFFFFFFFL; + buf[bufIdx++] = (byte) valid.validity; + intToBytes((int) valid.next, buf, bufIdx); bufIdx += 4; - valid.fullCount = (short) (buf[bufIdx++] & 0xFF); - valid.marginalCount = (short) (buf[bufIdx++] & 0xFF); + buf[bufIdx++] = (byte) valid.fullCount; + buf[bufIdx++] = (byte) valid.marginalCount; break; default: - throw new IllegalArgumentException("Unexpected TrustRecordType: " + type); + throw new IllegalArgumentException("Unexpected TrustRecordType: " + record.getType()); } - record.recordNum = recordNum; - putToCache(record); - } - else - { - if (expectedType != null && !expectedType.equals(record.getType())) - throw new IllegalStateException(String.format("expectedType != foundType :: %s != %s", expectedType, - record.getType())); - } - - return expectedTrustRecordClass.cast(record); - } - - public synchronized void putTrustRecord(final TrustRecord trustRecord) throws TrustDbIoException - { - assertNotNull("trustRecord", trustRecord); - - if (trustRecord.getRecordNum() < 0) - trustRecord.setRecordNum(newRecordNum()); - - putToCache(trustRecord); - - final long recordNum = trustRecord.getRecordNum(); - dirtyRecordNum2TrustRecord.put(recordNum, trustRecord); - - if (trustRecord instanceof TrustRecord.Trust) - updateHashTable(getTrustHashRec(), ((TrustRecord.Trust) trustRecord).getFingerprint(), recordNum); - } - protected synchronized void writeTrustRecord(final TrustRecord record) throws TrustDbIoException - { - int bufIdx = 0; - final byte[] buf = new byte[TRUST_RECORD_LEN]; - - buf[bufIdx++] = (byte) record.getType().getId(); - ++bufIdx; // Skip reserved byte. - - switch (record.getType()) - { - case UNUSED: // unused (free) record - break; - case VERSION: // version record - final TrustRecord.Version version = (TrustRecord.Version) record; - - --bufIdx; // undo skip reserved byte, because this does not apply to VERSION record. - buf[bufIdx++] = 'g'; - buf[bufIdx++] = 'p'; - buf[bufIdx++] = 'g'; - - buf[bufIdx++] = (byte) version.version; - buf[bufIdx++] = (byte) version.marginalsNeeded; - buf[bufIdx++] = (byte) version.completesNeeded; - buf[bufIdx++] = (byte) version.certDepth; - buf[bufIdx++] = (byte) version.trustModel; - buf[bufIdx++] = (byte) version.minCertLevel; - - bufIdx += 2; // no idea why, but we have to skip 2 bytes - - intToBytes((int) (version.created.getTime() / 1000L), buf, bufIdx); - bufIdx += 4; - intToBytes((int) (version.nextCheck.getTime() / 1000L), buf, bufIdx); - bufIdx += 4; - bufIdx += 4; // no idea why, but we have to skip 4 bytes - bufIdx += 4; // no idea why, but we have to skip 4 bytes - intToBytes((int) version.firstFree, buf, bufIdx); - bufIdx += 4; - bufIdx += 4; // no idea why, but we have to skip 4 bytes - intToBytes((int) version.trustHashTbl, buf, bufIdx); - bufIdx += 4; - - if (version.version != 3) - throw new TrustDbIoException(String.format("Wrong version number (3 expected, but %d found): %s", - version.version, file.getAbsolutePath())); - break; - case FREE: - final TrustRecord.Free free = (TrustRecord.Free) record; - intToBytes((int) free.next, buf, bufIdx); - bufIdx += 4; - break; - case HTBL: - final TrustRecord.HashTbl hashTbl = (TrustRecord.HashTbl) record; - for (int i = 0; i < ITEMS_PER_HTBL_RECORD; ++i) - { - intToBytes((int) hashTbl.item[i], buf, bufIdx); - bufIdx += 4; - } - break; - case HLST: - final TrustRecord.HashLst hashLst = (TrustRecord.HashLst) record; - intToBytes((int) hashLst.next, buf, bufIdx); - bufIdx += 4; - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) - { - intToBytes((int) hashLst.rnum[i], buf, bufIdx); - bufIdx += 4; - } - break; - case TRUST: - final TrustRecord.Trust trust = (TrustRecord.Trust) record; - System.arraycopy(trust.fingerprint, 0, buf, bufIdx, 20); - bufIdx += 20; - buf[bufIdx++] = (byte) trust.ownerTrust; - buf[bufIdx++] = (byte) trust.depth; - buf[bufIdx++] = (byte) trust.minOwnerTrust; - ++bufIdx; // no idea why, but we have to skip 1 byte - intToBytes((int) trust.validList, buf, bufIdx); - bufIdx += 4; - break; - case VALID: - final TrustRecord.Valid valid = (TrustRecord.Valid) record; - System.arraycopy(valid.nameHash, 0, buf, bufIdx, 20); - bufIdx += 20; - buf[bufIdx++] = (byte) valid.validity; - intToBytes((int) valid.next, buf, bufIdx); - bufIdx += 4; - buf[bufIdx++] = (byte) valid.fullCount; - buf[bufIdx++] = (byte) valid.marginalCount; - break; - default: - throw new IllegalArgumentException("Unexpected TrustRecordType: " + record.getType()); - } - - try - { - raf.seek(record.getRecordNum() * TRUST_RECORD_LEN); - raf.write(buf); - } catch (IOException e) - { - throw new TrustDbIoException(e); + try + { + raf.seek(record.getRecordNum() * TRUST_RECORD_LEN); + raf.write(buf); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } } } @@ -609,124 +676,126 @@ protected synchronized void writeTrustRecord(final TrustRecord record) throws Tr * Return: 0 on success or an error code. */ // static int upd_hashtable (ulong table, byte *key, int keylen, ulong newrecnum) - protected synchronized void updateHashTable(long table, byte[] key, long recordNum) throws TrustDbIoException + protected void updateHashTable(long table, byte[] key, long recordNum) throws TrustDbIoException { - // TrustRecord lastrec, rec; - TrustRecord.HashTbl lastHashTable = null; - long hashrec, item; - int msb; - int level = 0; - - hashrec = table; - next_level: while (true) - { - msb = key[level] & 0xff; - hashrec += msb / ITEMS_PER_HTBL_RECORD; - - TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); - item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); - if (item == 0) - { // Insert a new item into the hash table. - hashTable.setItem(msb % ITEMS_PER_HTBL_RECORD, recordNum); - putTrustRecord(hashTable); - return; - } - else if (item == recordNum) - { // perfect match ;-) - return; - } - else - { // Must do an update. - lastHashTable = hashTable; - hashTable = null; - TrustRecord rec = getTrustRecord(item); - if (rec.getType() == TrustRecordType.HTBL) - { - hashrec = item; - ++level; - if (level >= key.length) - throw new TrustDbIoException("hashtable has invalid indirections."); - - continue next_level; + synchronized (mutex) { + // TrustRecord lastrec, rec; + TrustRecord.HashTbl lastHashTable = null; + long hashrec, item; + int msb; + int level = 0; + + hashrec = table; + next_level: while (true) + { + msb = key[level] & 0xff; + hashrec += msb / ITEMS_PER_HTBL_RECORD; + + TrustRecord.HashTbl hashTable = getTrustRecord(hashrec, TrustRecord.HashTbl.class); + item = hashTable.getItem(msb % ITEMS_PER_HTBL_RECORD); + if (item == 0) + { // Insert a new item into the hash table. + hashTable.setItem(msb % ITEMS_PER_HTBL_RECORD, recordNum); + putTrustRecord(hashTable); + return; } - else if (rec.getType() == TrustRecordType.HLST) - { // Extend the list. - TrustRecord.HashLst hashList = (HashLst) rec; - // Check whether the key is already in this list. - for (;;) + else if (item == recordNum) + { // perfect match ;-) + return; + } + else + { // Must do an update. + lastHashTable = hashTable; + hashTable = null; + TrustRecord rec = getTrustRecord(item); + if (rec.getType() == TrustRecordType.HTBL) { - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) - { - if (hashList.getRNum(i) == recordNum) - return; // Okay, already in the list. - } + hashrec = item; + ++level; + if (level >= key.length) + throw new TrustDbIoException("hashtable has invalid indirections."); - if (hashList.getNext() == 0) - break; // key is not in the list - - hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); - assertNotNull("hashList", hashList); + continue next_level; } - - // The following line was added by me, Marco. I think the original GnuPG code missed this: We should - // start looking - // for a free entry in the *first* suitable HashList record again, because there might have been - // sth. dropped. - hashList = (HashLst) rec; - - // Find the next free entry and put it in. - for (;;) - { - for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + else if (rec.getType() == TrustRecordType.HLST) + { // Extend the list. + TrustRecord.HashLst hashList = (HashLst) rec; + // Check whether the key is already in this list. + for (;;) { - if (hashList.getRNum(i) == 0) + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) { - // Empty slot found. - hashList.setRnum(i, recordNum); - putTrustRecord(hashList); - return; // Done. + if (hashList.getRNum(i) == recordNum) + return; // Okay, already in the list. } - } - if (hashList.getNext() != 0) - { - // read the next reord of the list. + if (hashList.getNext() == 0) + break; // key is not in the list + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + assertNotNull("hashList", hashList); } - else + + // The following line was added by me, Marco. I think the original GnuPG code missed this: We should + // start looking + // for a free entry in the *first* suitable HashList record again, because there might have been + // sth. dropped. + hashList = (HashLst) rec; + + // Find the next free entry and put it in. + for (;;) { - // Append a new record to the list. - TrustRecord.HashLst old = hashList; - hashList = new TrustRecord.HashLst(); - hashList.setRnum(0, recordNum); - - putTrustRecord(hashList); // assigns the new recordNum, too - old.setNext(hashList.getRecordNum()); - putTrustRecord(old); - return; // Done. - } - } /* end loop over list slots */ - } - else - { // Insert a list record. - if (rec.getType() != TrustRecordType.TRUST) - throw new IllegalStateException(String.format( - "hashtbl %d: %d/%d points to an invalid record %d", - table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item)); - - if (rec.getRecordNum() == recordNum) - return; // found - fine - no need to change anything ;-) - - TrustRecord.HashLst hashList = new TrustRecord.HashLst(); - hashList.setRnum(0, rec.getRecordNum()); // Old key record - hashList.setRnum(1, recordNum); // and new key record - putTrustRecord(hashList); - - // Update the hashtable record. - assertNotNull("lastHashTable", lastHashTable).setItem(msb % ITEMS_PER_HTBL_RECORD, - hashList.getRecordNum()); - putTrustRecord(lastHashTable); - return; + for (int i = 0; i < ITEMS_PER_HLST_RECORD; ++i) + { + if (hashList.getRNum(i) == 0) + { + // Empty slot found. + hashList.setRnum(i, recordNum); + putTrustRecord(hashList); + return; // Done. + } + } + + if (hashList.getNext() != 0) + { + // read the next reord of the list. + hashList = getTrustRecord(hashList.getNext(), TrustRecord.HashLst.class); + } + else + { + // Append a new record to the list. + TrustRecord.HashLst old = hashList; + hashList = new TrustRecord.HashLst(); + hashList.setRnum(0, recordNum); + + putTrustRecord(hashList); // assigns the new recordNum, too + old.setNext(hashList.getRecordNum()); + putTrustRecord(old); + return; // Done. + } + } /* end loop over list slots */ + } + else + { // Insert a list record. + if (rec.getType() != TrustRecordType.TRUST) + throw new IllegalStateException(String.format( + "hashtbl %d: %d/%d points to an invalid record %d", + table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item)); + + if (rec.getRecordNum() == recordNum) + return; // found - fine - no need to change anything ;-) + + TrustRecord.HashLst hashList = new TrustRecord.HashLst(); + hashList.setRnum(0, rec.getRecordNum()); // Old key record + hashList.setRnum(1, recordNum); // and new key record + putTrustRecord(hashList); + + // Update the hashtable record. + assertNotNull("lastHashTable", lastHashTable).setItem(msb % ITEMS_PER_HTBL_RECORD, + hashList.getRecordNum()); + putTrustRecord(lastHashTable); + return; + } } } } @@ -758,37 +827,41 @@ private void putToCache(TrustRecord trustRecord) cacheRecordNums.add(recordNum); } - public synchronized void flush() throws TrustDbIoException + public void flush() throws TrustDbIoException { - for (TrustRecord trustRecord : dirtyRecordNum2TrustRecord.values()) - writeTrustRecord(trustRecord); + synchronized (mutex) { + for (TrustRecord trustRecord : dirtyRecordNum2TrustRecord.values()) + writeTrustRecord(trustRecord); - dirtyRecordNum2TrustRecord.clear(); + dirtyRecordNum2TrustRecord.clear(); - try - { - raf.getFD().sync(); - } catch (IOException e) - { - throw new TrustDbIoException(e); + try + { + raf.getFD().sync(); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } } } @Override - public synchronized void close() throws TrustDbIoException + public void close() throws TrustDbIoException { - if (closed) - return; + synchronized (mutex) { + if (closed) + return; - flush(); - closed = true; - try - { - fileLock.release(); - raf.close(); - } catch (IOException e) - { - throw new TrustDbIoException(e); + flush(); + closed = true; + try + { + fileLock.release(); + raf.close(); + } catch (IOException e) + { + throw new TrustDbIoException(e); + } } } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java index d72a68f786..4c06c34783 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java @@ -1,13 +1,27 @@ package org.bouncycastle.openpgp.wot.internal; +import static org.bouncycastle.openpgp.wot.internal.Util.*; + import java.util.Arrays; import java.util.Date; import org.bouncycastle.openpgp.wot.TrustConst; +/** + * A {@code TrustRecord} represents a row in the trust database - which is actually one big table. + *

    + * Each row in the trust database can have a different purpose, thus there are different types - + * modeled both via {@link TrustRecordType} and via sub-classes of {@code TrustRecord}. + *

    + * Important: Reading or modifying a TrustRecord must always be done in a synchronized block + * (using {@link Mutex}) guaranteeing the consistency of the entire trust database. There are + * inter-dependencies between different records inside the trust database and failing to synchronize + * might corrupt the entire database! + *

    + * Ported from tdbio.c: struct trust_record + */ public abstract class TrustRecord implements TrustConst { - protected long recordNum = -1; public static class Unused extends TrustRecord @@ -281,10 +295,6 @@ public static class Trust extends TrustRecord protected long validList; protected short minOwnerTrust; - public Trust() - { - } - @Override public TrustRecordType getType() { @@ -444,87 +454,4 @@ protected void setRecordNum(long recordNum) } public abstract TrustRecordType getType(); - - // Copied from tdbio.c: - // struct trust_record { - // int rectype; - // int mark; - // int dirty; /* for now only used internal by functions */ - // struct trust_record *next; /* help pointer to build lists in memory */ - // ulong recnum; - // union { - // struct { /* version record: */ - // byte version; /* should be 3 */ - // byte marginalsNeeded; - // byte completesNeeded; - // byte cert_depth; - // byte trust_model; - // byte min_cert_level; - // ulong created; /* timestamp of trustdb creation */ - // ulong nextcheck; /* timestamp of next scheduled check */ - // ulong reserved; - // ulong reserved2; - // ulong firstfree; - // ulong reserved3; - // ulong trusthashtbl; - // } ver; - // struct { /* free record */ - // ulong next; - // } free; - // struct { - // ulong item[ITEMS_PER_HTBL_RECORD]; - // } htbl; - // struct { - // ulong next; - // ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */ - // } hlst; - // struct { - // byte fingerprint[20]; - // byte ownertrust; - // byte depth; - // ulong validlist; - // byte min_ownertrust; - // } trust; - // struct { - // byte namehash[20]; - // ulong next; - // byte validity; - // byte full_count; - // byte marginal_count; - // } valid; - // } r; - // }; - - public static String encodeHexStr(final byte[] buf) - { - return encodeHexStr(buf, 0, buf.length); - } - - /** - * Encode a byte array into a human readable hex string. For each byte, two hex digits are produced. They are - * concatenated without any separators. - * - * @param buf - * The byte array to translate into human readable text. - * @param pos - * The start position (0-based). - * @param len - * The number of bytes that shall be processed beginning at the position specified by pos. - * @return a human readable string like "fa3d70" for a byte array with 3 bytes and these values. - * @see #encodeHexStr(byte[]) - * @see #decodeHexStr(String) - */ - public static String encodeHexStr(final byte[] buf, int pos, int len) - { - final StringBuilder hex = new StringBuilder(); - while (len-- > 0) - { - final byte ch = buf[pos++]; - int d = (ch >> 4) & 0xf; - hex.append((char) (d >= 10 ? 'a' - 10 + d : '0' + d)); - d = ch & 0xf; - hex.append((char) (d >= 10 ? 'a' - 10 + d : '0' + d)); - } - return hex.toString(); - } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java index 619c4139e1..ead71dd07e 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java @@ -16,8 +16,8 @@ public enum TrustRecordType VALID((short) 13, TrustRecord.Valid.class), FREE((short) 254, TrustRecord.Free.class); - private static Map id2Type; - private static Map, TrustRecordType> class2Type; + private static volatile Map id2Type; + private static volatile Map, TrustRecordType> class2Type; private final short id; private Class trustRecordClass; diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java index 737b1cebd5..a860d11aa2 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/Util.java @@ -1,5 +1,8 @@ package org.bouncycastle.openpgp.wot.internal; +import java.util.Collections; +import java.util.Iterator; + public class Util { private Util() @@ -149,4 +152,16 @@ public static final T assertNotNull(final String name, final T object) return object; } + + public static Iterator nullToEmpty(final Iterator iterator) + { + if (iterator == null) + return Collections. emptyList().iterator(); + else + return iterator; + } + + public static final void doNothing() + { + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java index 93fecb0bd2..410b3e8850 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java @@ -17,8 +17,6 @@ /** * OpenPGP key or key pair (if both public and secret key are present). - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class PgpKey { @@ -42,7 +40,7 @@ public class PgpKey private List subKeys; - private List pgpUserIds; + private volatile List pgpUserIds; public PgpKey(final PgpKeyId pgpKeyId, final PgpKeyFingerprint pgpKeyFingerprint) { @@ -142,8 +140,10 @@ protected void setMasterKey(PgpKey masterKey) public Set getSubKeyIds() { - if (subKeyIds == null && masterKey == null) // only a master-key can have sub-keys! hence we keep it null, if - // this is not a master-key! + if (masterKey != null) // only a master-key can have sub-keys! hence we keep it null, if this is not a master-key! + return null; + + if (subKeyIds == null) subKeyIds = new LinkedHashSet<>(); return subKeyIds; @@ -151,6 +151,9 @@ public Set getSubKeyIds() protected void setSubKeyIds(Set subKeyIds) { + if (masterKey != null) + throw new IllegalStateException("This is not a master-key! Cannot assign sub-keys!"); + this.subKeyIds = subKeyIds; } @@ -185,4 +188,26 @@ public String toString() return String.format("%s[pgpKeyId=%s masterKey=%s primaryUserId=%s]", this.getClass().getSimpleName(), pgpKeyId, masterKey, primaryUserId); } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((pgpKeyId == null) ? 0 : pgpKeyId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PgpKey other = (PgpKey) obj; + return this.pgpKeyId.equals(other.pgpKeyId); + } } diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java index f0c6d9025c..de58104aa2 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyFingerprint.java @@ -9,17 +9,15 @@ /** * An OpenPGP key's fingerprint. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class PgpKeyFingerprint implements Comparable, Serializable { private static final long serialVersionUID = 1L; private final byte[] fingerprint; - private transient int hashCode; - private transient WeakReference toString; - private transient WeakReference toHumanString; + private transient volatile int hashCode; + private transient volatile WeakReference toString; + private transient volatile WeakReference toHumanString; public PgpKeyFingerprint(final byte[] fingerprint) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java index b64b095166..3cfd892123 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyId.java @@ -7,16 +7,14 @@ /** * An OpenPGP key's (unique) identifier. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class PgpKeyId implements Comparable, Serializable { private static final long serialVersionUID = 1L; private final long pgpKeyId; - private transient WeakReference toString; - private transient WeakReference toHumanString; + private transient volatile WeakReference toString; + private transient volatile WeakReference toHumanString; public PgpKeyId(final long pgpKeyId) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java index 001ab8684b..22f9af2811 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistry.java @@ -18,11 +18,32 @@ * the timestamp changes, the files are re-loaded. But beware: The file system's timestamps usually have a pretty bad * resolution (of 1 or even 2 seconds). Therefore, it may happen that a modification goes undetected, if multiple * changes occur within the resolution. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public interface PgpKeyRegistry { + /** + * Utility class for creating an instance of a {@code PgpKeyRegistry} implementation. + */ + public static class Helper { + /** + * Creates a new instance of a {@code PgpKeyRegistry} implementation. + *

    + * There is currently only one single implementation available ({@code PgpKeyRegistryImpl}), + * but this might change in the future. Hence, this method should be used instead of + * directly invoking a constructor! + * @param pubringFile + * the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/} + * ). Must not be null. The file does not need to exist, though. + * @param secringFile + * the file containing the secret keys - usually named {@code secring.gpg} (located in {@code ~/.gnupg/} + * ). Must not be null. The file does not need to exist, though. + * @return a new instance of a {@code PgpKeyRegistry}. Never null. + */ + public static PgpKeyRegistry createInstance(final File pubringFile, final File secringFile) { + return new PgpKeyRegistryImpl(pubringFile, secringFile); + } + } + /** * Gets the file containing the public keys - usually named {@code pubring.gpg} (located in {@code ~/.gnupg/}). * diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java index c311a5aa68..2291a0aa3f 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKeyRegistryImpl.java @@ -29,13 +29,12 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; +import org.bouncycastle.openpgp.wot.internal.Mutex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of {@link PgpKeyRegistry}. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class PgpKeyRegistryImpl implements PgpKeyRegistry { @@ -43,6 +42,7 @@ public class PgpKeyRegistryImpl implements PgpKeyRegistry private final File pubringFile; private final File secringFile; + private final Mutex mutex; private long pubringFileLastModified = Long.MIN_VALUE; private long secringFileLastModified = Long.MIN_VALUE; @@ -67,6 +67,7 @@ public PgpKeyRegistryImpl(File pubringFile, File secringFile) { this.pubringFile = assertNotNull("pubringFile", pubringFile); this.secringFile = assertNotNull("secringFile", secringFile); + this.mutex = Mutex.forPubringFile(pubringFile); } @Override @@ -84,168 +85,184 @@ public File getSecringFile() @Override public PgpKey getPgpKeyOrFail(final PgpKeyId pgpKeyId) throws IllegalArgumentException { - final PgpKey pgpKey = getPgpKey(pgpKeyId); - if (pgpKey == null) - throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); + synchronized (mutex) { + final PgpKey pgpKey = getPgpKey(pgpKeyId); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this keyId: " + pgpKeyId); - return pgpKey; + return pgpKey; + } } @Override - public synchronized PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException + public PgpKey getPgpKey(final PgpKeyId pgpKeyId) throws IllegalArgumentException { - assertNotNull("pgpKeyId", pgpKeyId); - loadIfNeeded(); - final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); - return pgpKey; + synchronized (mutex) { + assertNotNull("pgpKeyId", pgpKeyId); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + return pgpKey; + } } @Override public PgpKey getPgpKeyOrFail(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException { - final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); - if (pgpKey == null) - throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); + synchronized (mutex) { + final PgpKey pgpKey = getPgpKey(pgpKeyFingerprint); + if (pgpKey == null) + throw new IllegalArgumentException("No PGP key found for this fingerprint: " + pgpKeyFingerprint); - return pgpKey; + return pgpKey; + } } @Override - public synchronized PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException + public PgpKey getPgpKey(final PgpKeyFingerprint pgpKeyFingerprint) throws IllegalArgumentException { - assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); - loadIfNeeded(); - final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); - return pgpKey; + synchronized (mutex) { + assertNotNull("pgpKeyFingerprint", pgpKeyFingerprint); + loadIfNeeded(); + final PgpKey pgpKey = pgpKeyFingerprint2pgpKey.get(pgpKeyFingerprint); + return pgpKey; + } } @Override - public synchronized Collection getMasterKeys() + public Collection getMasterKeys() { - loadIfNeeded(); - return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); + synchronized (mutex) { + loadIfNeeded(); + return Collections.unmodifiableCollection(pgpKeyId2masterKey.values()); + } } @Override public void markStale() { - pubringFileLastModified = Long.MIN_VALUE; - secringFileLastModified = Long.MIN_VALUE; + synchronized (mutex) { + pubringFileLastModified = Long.MIN_VALUE; + secringFileLastModified = Long.MIN_VALUE; + } } /** * Loads the key ring files, if they were not yet read or if this registry is stale. */ - protected synchronized void loadIfNeeded() + protected void loadIfNeeded() { - if (pgpKeyId2pgpKey == null - || getPubringFile().lastModified() != pubringFileLastModified - || getSecringFile().lastModified() != secringFileLastModified) - { - logger.debug("loadIfNeeded: invoking load()."); - load(); + synchronized (mutex) { + if (pgpKeyId2pgpKey == null + || getPubringFile().lastModified() != pubringFileLastModified + || getSecringFile().lastModified() != secringFileLastModified) + { + logger.debug("loadIfNeeded: invoking load()."); + load(); + } + else + logger.trace("loadIfNeeded: *not* invoking load()."); } - else - logger.trace("loadIfNeeded: *not* invoking load()."); } /** * Loads the key ring files. */ - protected synchronized void load() + protected void load() { - pgpKeyFingerprint2pgpKey = null; - final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); - final Map pgpKeyId2pgpKey = new HashMap<>(); - final Map pgpKeyId2masterKey = new HashMap<>(); - - final long pubringFileLastModified; - final long secringFileLastModified; - try - { - final File secringFile = getSecringFile(); - logger.debug("load: secringFile='{}'", secringFile); - secringFileLastModified = secringFile.lastModified(); - if (secringFile.isFile()) + synchronized (mutex) { + final Map pgpKeyFingerprint2pgpKey = new HashMap<>(); + final Map pgpKeyId2pgpKey = new HashMap<>(); + final Map pgpKeyId2masterKey = new HashMap<>(); + + final long pubringFileLastModified; + final long secringFileLastModified; + try { - final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; - try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) - { - pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), - new BcKeyFingerprintCalculator()); - } - for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext();) + final File secringFile = getSecringFile(); + logger.debug("load: secringFile='{}'", secringFile); + secringFileLastModified = secringFile.lastModified(); + if (secringFile.isFile()) { - final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); - PgpKey masterKey = null; - for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(secringFile));) { - final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); - masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, - pgpKeyId2masterKey, masterKey, keyRing, publicKey); + pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in), + new BcKeyFingerprintCalculator()); } - - for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext();) + for (final Iterator it1 = pgpSecretKeyRingCollection.getKeyRings(); it1.hasNext();) { - final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); - final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); - final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); - if (pgpKey == null) - throw new IllegalStateException( - "Secret key does not have corresponding public key in secret key ring! pgpKeyId=" - + pgpKeyId); - - pgpKey.setSecretKey(secretKey); - logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); + final PGPSecretKeyRing keyRing = (PGPSecretKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } + + for (final Iterator it3 = keyRing.getSecretKeys(); it3.hasNext();) + { + final PGPSecretKey secretKey = (PGPSecretKey) it3.next(); + final PgpKeyId pgpKeyId = new PgpKeyId(secretKey.getKeyID()); + final PgpKey pgpKey = pgpKeyId2pgpKey.get(pgpKeyId); + if (pgpKey == null) + throw new IllegalStateException( + "Secret key does not have corresponding public key in secret key ring! pgpKeyId=" + + pgpKeyId); + + pgpKey.setSecretKey(secretKey); + logger.debug("load: read secretKey with pgpKeyId={}", pgpKeyId); + } } } - } - final File pubringFile = getPubringFile(); - logger.debug("load: pubringFile='{}'", pubringFile); - pubringFileLastModified = pubringFile.lastModified(); - if (pubringFile.isFile()) - { - final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; - try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) + final File pubringFile = getPubringFile(); + logger.debug("load: pubringFile='{}'", pubringFile); + pubringFileLastModified = pubringFile.lastModified(); + if (pubringFile.isFile()) { - pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), - new BcKeyFingerprintCalculator()); - } + final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; + try (InputStream in = new BufferedInputStream(new FileInputStream(pubringFile));) + { + pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), + new BcKeyFingerprintCalculator()); + } - for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext();) - { - final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); - PgpKey masterKey = null; - for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + for (final Iterator it1 = pgpPublicKeyRingCollection.getKeyRings(); it1.hasNext();) { - final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); - masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, - pgpKeyId2masterKey, masterKey, keyRing, publicKey); + final PGPPublicKeyRing keyRing = (PGPPublicKeyRing) it1.next(); + PgpKey masterKey = null; + for (final Iterator it2 = keyRing.getPublicKeys(); it2.hasNext();) + { + final PGPPublicKey publicKey = (PGPPublicKey) it2.next(); + masterKey = enlistPublicKey(pgpKeyFingerprint2pgpKey, pgpKeyId2pgpKey, + pgpKeyId2masterKey, masterKey, keyRing, publicKey); + } } } + } catch (IOException | PGPException x) + { + throw new RuntimeException(x); } - } catch (IOException | PGPException x) - { - throw new RuntimeException(x); - } - for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) - { - if (pgpKey.getPublicKey() == null) - throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) + { + if (pgpKey.getPublicKey() == null) + throw new IllegalStateException("pgpKey.publicKey == null :: keyId = " + pgpKey.getPgpKeyId()); - if (pgpKey.getPublicKeyRing() == null) - throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); - } + if (pgpKey.getPublicKeyRing() == null) + throw new IllegalStateException("pgpKey.publicKeyRing == null :: keyId = " + pgpKey.getPgpKeyId()); + } - this.secringFileLastModified = secringFileLastModified; - this.pubringFileLastModified = pubringFileLastModified; - this.pgpKeyFingerprint2pgpKey = pgpKeyFingerprint2pgpKey; - this.pgpKeyId2pgpKey = pgpKeyId2pgpKey; - this.pgpKeyId2masterKey = pgpKeyId2masterKey; + this.secringFileLastModified = secringFileLastModified; + this.pubringFileLastModified = pubringFileLastModified; + this.pgpKeyFingerprint2pgpKey = Collections.unmodifiableMap(pgpKeyFingerprint2pgpKey); + this.pgpKeyId2pgpKey = Collections.unmodifiableMap(pgpKeyId2pgpKey); + this.pgpKeyId2masterKey = Collections.unmodifiableMap(pgpKeyId2masterKey); + this.signingKeyId2signedKeyIds = null; - assignSubKeys(); + assignSubKeys(); + } } private void assignSubKeys() @@ -311,123 +328,132 @@ else if (keyRing instanceof PGPPublicKeyRing) return masterKey; } - /* (non-Javadoc) - * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#getPgpKeyFingerprintsSignedBy(org.bouncycastle.openpgp.wot.key.PgpKeyFingerprint) - */ @Override - public synchronized Set getPgpKeyFingerprintsSignedBy( + public Set getPgpKeyFingerprintsSignedBy( final PgpKeyFingerprint signingPgpKeyFingerprint) { - assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); - final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); - if (signingPgpKey == null) - return Collections.emptySet(); - - final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); - if (pgpKeyIds == null) - return Collections.emptySet(); - - final Set result = new HashSet<>(pgpKeyIds.size()); - for (final PgpKeyId pgpKeyId : pgpKeyIds) - { - final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); - result.add(pgpKey.getPgpKeyFingerprint()); + synchronized (mutex) { + assertNotNull("signingPgpKeyFingerprint", signingPgpKeyFingerprint); + final PgpKey signingPgpKey = getPgpKey(signingPgpKeyFingerprint); + if (signingPgpKey == null) + return Collections.emptySet(); + + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKey.getPgpKeyId()); + if (pgpKeyIds == null) + return Collections.emptySet(); + + final Set result = new HashSet<>(pgpKeyIds.size()); + for (final PgpKeyId pgpKeyId : pgpKeyIds) + { + final PgpKey pgpKey = getPgpKeyOrFail(pgpKeyId); + result.add(pgpKey.getPgpKeyFingerprint()); + } + return Collections.unmodifiableSet(result); } - return Collections.unmodifiableSet(result); } - /* (non-Javadoc) - * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#getPgpKeyIdsSignedBy(org.bouncycastle.openpgp.wot.key.PgpKeyId) - */ @Override public Set getPgpKeyIdsSignedBy(final PgpKeyId signingPgpKeyId) { - final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); - if (pgpKeyIds == null) - return Collections.emptySet(); + synchronized (mutex) { + final Set pgpKeyIds = getSigningKeyId2signedKeyIds().get(signingPgpKeyId); + if (pgpKeyIds == null) + return Collections.emptySet(); - return Collections.unmodifiableSet(pgpKeyIds); + return Collections.unmodifiableSet(pgpKeyIds); + } } - protected synchronized Map> getSigningKeyId2signedKeyIds() + protected Map> getSigningKeyId2signedKeyIds() { - loadIfNeeded(); - if (signingKeyId2signedKeyIds == null) - { - final Map> m = new HashMap<>(); - for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) + synchronized (mutex) { + loadIfNeeded(); + if (signingKeyId2signedKeyIds == null) { - final PGPPublicKey publicKey = pgpKey.getPublicKey(); - for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) + final Map> m = new HashMap<>(); + for (final PgpKey pgpKey : pgpKeyId2pgpKey.values()) { - if (pgpUserId.getUserId() != null) + final PGPPublicKey publicKey = pgpKey.getPublicKey(); + for (final PgpUserId pgpUserId : pgpKey.getPgpUserIds()) { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + if (pgpUserId.getUserId() != null) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } else if (pgpUserId.getUserAttribute() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } else if (pgpUserId.getUserAttribute() != null) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); - } - } else - throw new IllegalStateException("WTF?!"); - } + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } + } else + throw new IllegalStateException("WTF?!"); + } - // It seems, there are both: certifications for individual - // user-ids and certifications for the - // entire key. I therefore first take the individual ones - // (above) into account then and then - // the ones for the entire key (below). - // Normally, the signatures bound to the key are never - // 'certifications', but it rarely happens. - // Don't know, if these are malformed or deprecated (very old) - // keys, but I should take them into account. - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (isCertification(pgpSignature)) - enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + // It seems, there are both: certifications for individual user-ids and certifications for the + // entire key. I therefore first take the individual ones (above) into account then and then + // the ones for the entire key (below). Normally, the signatures bound to the key are never + // 'certifications', but it rarely happens. Don't know, if these are malformed or deprecated (very old) + // keys, but I should take them into account. + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (isCertification(pgpSignature)) + enlistInSigningKey2signedKeyIds(m, pgpKey, pgpSignature); + } } + signingKeyId2signedKeyIds = Collections.unmodifiableMap(m); } - signingKeyId2signedKeyIds = m; + return signingKeyId2signedKeyIds; } - return signingKeyId2signedKeyIds; } - /* (non-Javadoc) - * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#getSignatures(org.bouncycastle.openpgp.wot.key.PgpUserId) - */ @Override - public synchronized List getSignatures(final PgpUserId pgpUserId) + public List getSignatures(final PgpUserId pgpUserId) { - assertNotNull("pgpUserId", pgpUserId); - final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); + synchronized (mutex) { + assertNotNull("pgpUserId", pgpUserId); + final PGPPublicKey publicKey = pgpUserId.getPgpKey().getPublicKey(); - final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); + final IdentityHashMap pgpSignatures = new IdentityHashMap<>(); - final List result = new ArrayList<>(); - if (pgpUserId.getUserId() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) + final List result = new ArrayList<>(); + if (pgpUserId.getUserId() != null) { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForID(pgpUserId.getUserId())); it.hasNext();) { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } } } - } - else if (pgpUserId.getUserAttribute() != null) - { - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) + else if (pgpUserId.getUserAttribute() != null) + { + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getSignaturesForUserAttribute(pgpUserId.getUserAttribute())); it.hasNext();) + { + final PGPSignature pgpSignature = (PGPSignature) it.next(); + if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) + { + pgpSignatures.put(pgpSignature, pgpSignature); + result.add(pgpSignature); + } + } + } + else + throw new IllegalStateException("WTF?!"); + + // There are also key-signatures which are not for a certain indivdual user-id/-attribute, but for the entire key. + // See the comment in getSigningKeyId2signedKeyIds() above for more details. + for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) { final PGPSignature pgpSignature = (PGPSignature) it.next(); if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) @@ -436,31 +462,9 @@ else if (pgpUserId.getUserAttribute() != null) result.add(pgpSignature); } } - } - else - throw new IllegalStateException("WTF?!"); - // There are also key-signatures which are not for a certain indivdual user-id/-attribute, but for the entire key. - // See the comment in getSigningKeyId2signedKeyIds() above for more details. - for (@SuppressWarnings("unchecked") final Iterator it = nullToEmpty(publicKey.getKeySignatures()); it.hasNext();) - { - final PGPSignature pgpSignature = (PGPSignature) it.next(); - if (!pgpSignatures.containsKey(pgpSignature) && isCertification(pgpSignature)) - { - pgpSignatures.put(pgpSignature, pgpSignature); - result.add(pgpSignature); - } + return result; } - - return result; - } - - protected static Iterator nullToEmpty(final Iterator iterator) - { - if (iterator == null) - return Collections. emptyList().iterator(); - else - return iterator; } private void enlistInSigningKey2signedKeyIds(final Map> signingKeyId2signedKeyIds, @@ -476,9 +480,6 @@ private void enlistInSigningKey2signedKeyIds(final Map> signedKeyIds.add(pgpKey.getPgpKeyId()); } - /* (non-Javadoc) - * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#isCertification(org.bouncycastle.openpgp.PGPSignature) - */ @Override public boolean isCertification(final PGPSignature pgpSignature) { @@ -486,9 +487,6 @@ public boolean isCertification(final PGPSignature pgpSignature) return isCertification(pgpSignature.getSignatureType()); } - /* (non-Javadoc) - * @see org.bouncycastle.openpgp.wot.key.PgpKeyRegistry#isCertification(int) - */ @Override public boolean isCertification(int pgpSignatureType) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java index eea3ef9c54..db5bb6cbe0 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserId.java @@ -6,15 +6,13 @@ /** * User-identity or user-attribute of an OpenPGP key. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class PgpUserId { private final PgpKey pgpKey; private final String userId; private final PGPUserAttributeSubpacketVector userAttribute; - private PgpUserIdNameHash nameHash; + private volatile PgpUserIdNameHash nameHash; public PgpUserId(final PgpKey pgpKey, final String userId) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java index 04f388ee35..48a22261c6 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpUserIdNameHash.java @@ -18,17 +18,15 @@ *

    * Use {@link #createFromUserId(String)} or {@link #createFromUserAttribute(PGPUserAttributeSubpacketVector)} to create * an instance. - * - * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co */ public class PgpUserIdNameHash implements Comparable, Serializable { private static final long serialVersionUID = 1L; private final byte[] namehash; - private transient int hashCode; - private transient WeakReference toString; - private transient WeakReference toHumanString; + private transient volatile int hashCode; + private transient volatile WeakReference toString; + private transient volatile WeakReference toHumanString; protected PgpUserIdNameHash(final byte[] namehash) { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java index 332c84a27f..336d4509f6 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/package-info.java @@ -1,7 +1,7 @@ /** * OpenPGP Web Of Trust (WOT) implementation. *

    - * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.TrustDbImpl TrustDbImpl}. + * The most important class and entry point is {@link org.bouncycastle.openpgp.wot.internal.TrustDbImpl TrustDbImpl}. */ package org.bouncycastle.openpgp.wot; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java index a87c9aa1b8..6ad882a04f 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; +import org.bouncycastle.openpgp.wot.internal.Mutex; +import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; import org.bouncycastle.openpgp.wot.internal.TrustDbIo; import org.bouncycastle.openpgp.wot.internal.TrustRecord; import org.bouncycastle.openpgp.wot.internal.TrustRecord.Trust; @@ -26,10 +28,13 @@ public class TrustDbProductiveFileTest extends AbstractTrustDbTest { private static final Logger logger = LoggerFactory.getLogger(TrustDbProductiveFileTest.class); + private Mutex mutex; + @Override protected void initGnupgHomeDir() { String userHome = System.getProperty("user.home"); gnupgHomeDir = new File(userHome, ".gnupg"); + mutex = Mutex.forPgpDir(gnupgHomeDir); } @Override @@ -39,7 +44,7 @@ protected void deleteGnupgHomeDir() { @Test public void readMyProductiveTrustDb() throws Exception { - try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile);) { + try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile, mutex);) { long recordNum = -1; TrustRecord trustRecord; List trustFingerprints = new ArrayList<>(); @@ -58,7 +63,7 @@ public void readMyProductiveTrustDb() throws Exception { @Test public void updateMyProductiveDbHashTable() throws Exception { - try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile);) { + try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile, mutex);) { long recordNum = -1; TrustRecord trustRecord; List trusts = new ArrayList<>(); @@ -78,7 +83,7 @@ public void readBrokenRecord() throws Exception { byte[] fingerprint = new byte[] { -5, 17, -44, -69, 123, 36, 70, 120, 51, 122, -83, -117, -57, -65, 38, -48, -69, 97, 120, 102 }; - try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile);) { + try (TrustDbIo trustDbIo = new TrustDbIo(trustdbFile, mutex);) { TrustRecord.Trust trust = trustDbIo.getTrustByFingerprint(fingerprint); if (trust == null) { long recordNum = -1; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java index 1661ba218e..00f6df3bd9 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/UpdateTrustDbTest.java @@ -4,6 +4,7 @@ import static org.bouncycastle.openpgp.PGPSignature.*; import static org.bouncycastle.openpgp.wot.TrustConst.*; +import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; import org.bouncycastle.openpgp.wot.key.PgpKey; import org.junit.Test; From 9590297bf73aa27b713e7b9c3a0e3304a4c18964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Thu, 1 Oct 2015 14:47:44 +0700 Subject: [PATCH 15/17] Made TrustRecord (and its sub-classes) package-protected. --- .../openpgp/wot/internal/TrustRecord.java | 16 ++++++++-------- .../TrustDbProductiveFileTest.java | 4 +++- 2 files changed, 11 insertions(+), 9 deletions(-) rename pgwot/src/test/java/org/bouncycastle/openpgp/wot/{ => internal}/TrustDbProductiveFileTest.java (97%) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java index 4c06c34783..3b81999870 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecord.java @@ -20,11 +20,11 @@ *

    * Ported from tdbio.c: struct trust_record */ -public abstract class TrustRecord implements TrustConst +abstract class TrustRecord implements TrustConst { protected long recordNum = -1; - public static class Unused extends TrustRecord + static class Unused extends TrustRecord { @Override public TrustRecordType getType() @@ -33,7 +33,7 @@ public TrustRecordType getType() } } - public static class Version extends TrustRecord + static class Version extends TrustRecord { protected short version; // should be 3 protected short marginalsNeeded; @@ -180,7 +180,7 @@ public String toString() } } - public static class Free extends TrustRecord + static class Free extends TrustRecord { protected long next; @@ -211,7 +211,7 @@ public String toString() } } - public static class HashTbl extends TrustRecord + static class HashTbl extends TrustRecord { protected long[] item = new long[ITEMS_PER_HTBL_RECORD]; @@ -242,7 +242,7 @@ public String toString() } } - public static class HashLst extends TrustRecord + static class HashLst extends TrustRecord { protected long next; protected long[] rnum = new long[ITEMS_PER_HLST_RECORD]; // of another record @@ -287,7 +287,7 @@ public String toString() } } - public static class Trust extends TrustRecord + static class Trust extends TrustRecord { protected byte[] fingerprint = new byte[20]; protected short ownerTrust; @@ -367,7 +367,7 @@ public String toString() } } - public static class Valid extends TrustRecord + static class Valid extends TrustRecord { protected byte[] nameHash = new byte[20]; protected long next; diff --git a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/internal/TrustDbProductiveFileTest.java similarity index 97% rename from pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java rename to pgwot/src/test/java/org/bouncycastle/openpgp/wot/internal/TrustDbProductiveFileTest.java index 6ad882a04f..515a8e3205 100644 --- a/pgwot/src/test/java/org/bouncycastle/openpgp/wot/TrustDbProductiveFileTest.java +++ b/pgwot/src/test/java/org/bouncycastle/openpgp/wot/internal/TrustDbProductiveFileTest.java @@ -1,4 +1,4 @@ -package org.bouncycastle.openpgp.wot; +package org.bouncycastle.openpgp.wot.internal; import static org.assertj.core.api.Assertions.*; import static org.bouncycastle.openpgp.wot.TrustConst.*; @@ -10,6 +10,8 @@ import java.util.List; import java.util.Map; +import org.bouncycastle.openpgp.wot.AbstractTrustDbTest; +import org.bouncycastle.openpgp.wot.TrustDb; import org.bouncycastle.openpgp.wot.internal.Mutex; import org.bouncycastle.openpgp.wot.internal.TrustDbImpl; import org.bouncycastle.openpgp.wot.internal.TrustDbIo; From 295f7a89aa29092545e8024534f401c2c5b1fb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Thu, 1 Oct 2015 14:50:24 +0700 Subject: [PATCH 16/17] Most classes in org.bouncycastle.openpgp.wot.internal are now package-protected. --- .../org/bouncycastle/openpgp/wot/TrustDbIoException.java | 5 ++--- .../org/bouncycastle/openpgp/wot/internal/TrustDbIo.java | 2 +- .../bouncycastle/openpgp/wot/internal/TrustRecordType.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java index beb7268155..e1e44a990e 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/TrustDbIoException.java @@ -1,9 +1,8 @@ package org.bouncycastle.openpgp.wot; -import org.bouncycastle.openpgp.wot.internal.TrustDbIo; - /** - * Exception thrown by {@link TrustDbIo} when reading from or writing to the trust database failed. + * Exception thrown by {@link org.bouncycastle.openpgp.wot.internal.TrustDbIo TrustDbIo} + * when reading from or writing to the trust database file failed. */ public class TrustDbIoException extends TrustDbException { diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java index 8e266ca4eb..d0ef6d2179 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustDbIo.java @@ -36,7 +36,7 @@ *

    * This class was mostly ported from the GnuPG's {@code tdbio.h} and {@code tdbio.c} files. */ -public class TrustDbIo implements AutoCloseable, TrustConst +class TrustDbIo implements AutoCloseable, TrustConst { private static final Logger logger = LoggerFactory.getLogger(TrustDbIo.class); diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java index ead71dd07e..9df8b8ba99 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/internal/TrustRecordType.java @@ -6,7 +6,7 @@ import java.util.HashMap; import java.util.Map; -public enum TrustRecordType +enum TrustRecordType { UNUSED((short) 0, TrustRecord.Unused.class), VERSION((short) 1, TrustRecord.Version.class), From d7949a9e2fcd5c280c4eb6f32e8e37e1e64fef3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20=E0=B8=AB=E0=B8=87=E0=B8=B8=E0=B9=88=E0=B8=A2?= =?UTF-8?q?=E0=B8=95=E0=B8=A3=E0=B8=B0=E0=B8=81=E0=B8=B9=E0=B8=A5-Schulze?= Date: Thu, 1 Oct 2015 16:05:22 +0700 Subject: [PATCH 17/17] Removed unnecessary null-check (final field is checked when being assigned). --- .../src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java index 410b3e8850..446ca0e6ac 100644 --- a/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java +++ b/pgwot/src/main/java/org/bouncycastle/openpgp/wot/key/PgpKey.java @@ -194,7 +194,7 @@ public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((pgpKeyId == null) ? 0 : pgpKeyId.hashCode()); + result = prime * result + pgpKeyId.hashCode(); return result; }