diff --git a/pgpainless-wot/src/main/kotlin/org/pgpainless/wot/WebOfTrust.kt b/pgpainless-wot/src/main/kotlin/org/pgpainless/wot/WebOfTrust.kt index 5c2aeb29..53b3507b 100644 --- a/pgpainless-wot/src/main/kotlin/org/pgpainless/wot/WebOfTrust.kt +++ b/pgpainless-wot/src/main/kotlin/org/pgpainless/wot/WebOfTrust.kt @@ -18,7 +18,6 @@ import org.pgpainless.signature.SignatureUtils import org.pgpainless.signature.consumer.SignatureVerifier import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.wot.dijkstra.sq.* -import org.pgpainless.wot.dijkstra.sq.CertificationSet.Companion.fromCertification import org.pgpainless.wot.dijkstra.sq.ReferenceTime.Companion.now import org.pgpainless.wot.util.CertificationFactory.Companion.fromCertification import org.pgpainless.wot.util.CertificationFactory.Companion.fromDelegation @@ -46,11 +45,10 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) { */ constructor(certificateDirectory: PGPCertificateDirectory): this(PGPCertificateStoreAdapter(certificateDirectory)) - lateinit var network: Network - - fun initialize() { + fun buildNetwork(policy: Policy = PGPainless.getPolicy(), referenceTime: ReferenceTime = now()): Network { val certificates = getAllCertificatesFromTheStore() - network = fromCertificates(certificates, PGPainless.getPolicy(), now()) + val networkFactory = PGPNetworkFactory.fromCertificates(certificates, policy, referenceTime) + return networkFactory.buildNetwork() } private fun getAllCertificatesFromTheStore(): Sequence { @@ -69,47 +67,181 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) { return certificates } - companion object { - @JvmStatic - fun fromCertificates(certificates: Sequence, - policy: Policy, - referenceTime: ReferenceTime): Network { - return fromValidCertificates( - parseValidCertificates(certificates, policy, referenceTime), - policy, - referenceTime - ) - } + /** + * Class for building the [Flow network][Network] from the given set of OpenPGP keys. + */ + private class PGPNetworkFactory private constructor(validatedCertificates: List, + private val policy: Policy, + private val referenceTime: ReferenceTime) { - @JvmStatic - fun fromValidCertificates(certificates: List, - policy: Policy, - referenceTime: ReferenceTime): Network { - val nb = NetworkBuilder(certificates, policy, referenceTime) - return nb.buildNetwork() - } + companion object { + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(PGPNetworkFactory::class.java) - @JvmStatic - private fun parseValidCertificates(certificates: Sequence, - policy: Policy, - referenceTime: ReferenceTime): List { - return certificates - .mapNotNull { cert -> - try { - PGPainless.readKeyRing().publicKeyRing(cert.inputStream) - } catch (e: IOException) { - null + @JvmStatic + fun fromCertificates(certificates: Sequence, + policy: Policy, + referenceTime: ReferenceTime): PGPNetworkFactory { + return fromValidCertificates( + parseValidCertificates(certificates, policy, referenceTime), + policy, + referenceTime + ) + } + + @JvmStatic + fun fromValidCertificates(certificates: List, + policy: Policy, + referenceTime: ReferenceTime): PGPNetworkFactory { + return PGPNetworkFactory(certificates, policy, referenceTime) + } + + @JvmStatic + private fun parseValidCertificates(certificates: Sequence, + policy: Policy, + referenceTime: ReferenceTime): List { + return certificates + .mapNotNull { + try { PGPainless.readKeyRing().publicKeyRing(it.inputStream) } catch (e: IOException) { null } } - } - .map { cert -> - KeyRingInfo(cert, policy, referenceTime.timestamp) - } - .toList() + .map { KeyRingInfo(it, policy, referenceTime.timestamp) } + .toList() + } } - // Map signature to its revocation state - @JvmStatic - private fun revocationStateFromSignature(revocation: PGPSignature?): RevocationState { + private val networkBuilder: Network.Builder = Network.builder() + + // certificates keyed by fingerprint + private val byFingerprint: MutableMap = HashMap() + + // certificates keyed by (sub-) key-id + private val byKeyId: MutableMap> = HashMap() + + // certificate synopses keyed by fingerprint + private val certSynopsisMap: MutableMap = HashMap() + + init { + validatedCertificates.forEach { indexAsNode(it) } + validatedCertificates.forEach { findEdgesWithTarget(it) } + } + + private fun indexAsNode(cert: KeyRingInfo) { + + // index by fingerprint + val certFingerprint = Fingerprint(cert.fingerprint) + if (!byFingerprint.containsKey(certFingerprint)) { + byFingerprint[certFingerprint] = cert + } + + // index by key-ID + var certsWithKey = byKeyId[cert.keyId] + // noinspection Java8MapApi + if (certsWithKey == null) { + certsWithKey = mutableListOf() + // TODO: Something is fishy here... + for (key in cert.validSubkeys) { + byKeyId[key.keyID] = certsWithKey + } + } + certsWithKey.add(cert) + val userIds: MutableMap = HashMap() + for (userId in cert.userIds) { + val state = RevocationState(cert.getUserIdRevocation(userId)) + userIds[userId] = state + } + + // index synopses + val expirationDate: Date? = try { + cert.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER) + } catch (e: NoSuchElementException) { + // Some keys are malformed and have no KeyFlags + return + } + val node = CertSynopsis(certFingerprint, + expirationDate, + RevocationState(cert.revocationSelfSignature), + userIds) + certSynopsisMap[certFingerprint] = node + networkBuilder.addNode(node) + } + + private fun findEdgesWithTarget(validatedTarget: KeyRingInfo) { + val validatedTargetKeyRing = KeyRingUtils.publicKeys(validatedTarget.keys) + val targetFingerprint = Fingerprint(OpenPgpFingerprint.of(validatedTargetKeyRing)) + val targetPrimaryKey = validatedTargetKeyRing.publicKey!! + val target = certSynopsisMap[targetFingerprint]!! + + // Direct-Key Signatures (delegations) by X on Y + val delegations = SignatureUtils.getDelegations(validatedTargetKeyRing) + for (delegation in delegations) { + processDelegation(targetPrimaryKey, target, delegation) + } + + // Certification Signatures by X on Y over user-ID U + val userIds = targetPrimaryKey.userIDs + while (userIds.hasNext()) { + val userId = userIds.next() + val userIdSigs = SignatureUtils.get3rdPartyCertificationsFor(userId, validatedTargetKeyRing) + userIdSigs.forEach { + processCertificationOnUserId(targetPrimaryKey, target, userId, it) + } + + } + } + + private fun processDelegation(targetPrimaryKey: PGPPublicKey, + target: CertSynopsis, + delegation: PGPSignature) { + val issuerCandidates = byKeyId[delegation.keyID] + ?: return + for (candidate in issuerCandidates) { + val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys) + val issuerFingerprint = Fingerprint(OpenPgpFingerprint.of(issuerKeyRing)) + val issuerSigningKey = issuerKeyRing.getPublicKey(delegation.keyID) + val issuer = certSynopsisMap[issuerFingerprint] + ?: continue + try { + val valid = SignatureVerifier.verifyDirectKeySignature(delegation, issuerSigningKey, + targetPrimaryKey, policy, referenceTime.timestamp) + if (valid) { + networkBuilder.addEdge(fromDelegation(issuer, target, delegation)) + } + } catch (e: SignatureValidationException) { + val targetFingerprint = OpenPgpFingerprint.of(targetPrimaryKey) + LOGGER.warn("Cannot verify signature by $issuerFingerprint on cert of $targetFingerprint", e) + } + } + } + + private fun processCertificationOnUserId(targetPrimaryKey: PGPPublicKey, + target: CertSynopsis, + userId: String, + certification: PGPSignature) { + val issuerCandidates = byKeyId[certification.keyID] + ?: return + for (candidate in issuerCandidates) { + val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys) + val issuerFingerprint = Fingerprint(OpenPgpFingerprint.of(issuerKeyRing)) + val issuerSigningKey = issuerKeyRing.getPublicKey(certification.keyID) + ?: continue + val issuer = certSynopsisMap[issuerFingerprint] + ?: continue + try { + val valid = SignatureVerifier.verifySignatureOverUserId(userId, certification, + issuerSigningKey, targetPrimaryKey, policy, referenceTime.timestamp) + if (valid) { + networkBuilder.addEdge(fromCertification(issuer, target, userId, certification)) + } + } catch (e: SignatureValidationException) { + LOGGER.warn("Cannot verify signature for '$userId' by $issuerFingerprint" + + " on cert of ${target.fingerprint}", e) + } + } + } + + private fun Fingerprint(fingerprint: OpenPgpFingerprint) = Fingerprint(fingerprint.toString()) + + private fun RevocationState(revocation: PGPSignature?): RevocationState { if (revocation == null) { return RevocationState.notRevoked() } @@ -121,209 +253,13 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) { RevocationState.softRevoked(revocation.creationTime) } - @JvmStatic - private fun OpenPgpFingerprint.map(): Fingerprint { - return Fingerprint(toString()) - } - /** - * Class for building the [Flow network][Network] from the given set of OpenPGP keys. + * Return the constructed, initialized [Network]. * + * @return finished network */ - private class NetworkBuilder constructor(validatedCertificates: List, - private val policy: Policy, - private val referenceTime: ReferenceTime) { - - companion object { - @JvmStatic - private val LOGGER = LoggerFactory.getLogger(NetworkBuilder::class.java) - } - - // certificates keyed by fingerprint - private val byFingerprint: MutableMap = HashMap() - - // certificates keyed by (sub-) key-id - private val byKeyId: MutableMap> = HashMap() - - // certificate synopses keyed by fingerprint - private val certSynopsisMap: MutableMap = HashMap() - - // Issuer -> Targets, edges keyed by issuer - private val edges: MutableMap> = HashMap() - - // Target -> Issuers, edges keyed by target - private val reverseEdges: MutableMap> = HashMap() - - init { - synopsizeCertificates(validatedCertificates) - findEdges(validatedCertificates) - } - - private fun synopsizeCertificates(validatedCertificates: List) { - for (cert in validatedCertificates) { - synopsize(cert) - } - } - - private fun synopsize(cert: KeyRingInfo) { - - // index by fingerprint - val certFingerprint = cert.fingerprint.map() - if (!byFingerprint.containsKey(certFingerprint)) { - byFingerprint[certFingerprint] = cert - } - - // index by key-ID - var certsWithKey = byKeyId[cert.keyId] - // noinspection Java8MapApi - if (certsWithKey == null) { - certsWithKey = mutableListOf() - // TODO: Something is fishy here... - for (key in cert.validSubkeys) { - byKeyId[key.keyID] = certsWithKey - } - } - certsWithKey.add(cert) - val userIds: MutableMap = HashMap() - for (userId in cert.userIds) { - val state: RevocationState = revocationStateFromSignature(cert.getUserIdRevocation(userId)) - userIds[userId] = state - } - - // index synopses - val expirationDate: Date? = try { - cert.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER) - } catch (e: NoSuchElementException) { - // Some keys are malformed and have no KeyFlags - return - } - certSynopsisMap[certFingerprint] = CertSynopsis(certFingerprint, - expirationDate, - revocationStateFromSignature(cert.revocationSelfSignature), - userIds) - } - - private fun findEdges(validatedCertificates: List) { - // Identify certifications and delegations - // Target = cert carrying a signature - for (validatedTarget in validatedCertificates) { - findEdgesWithTarget(validatedTarget) - } - } - - private fun findEdgesWithTarget(validatedTarget: KeyRingInfo) { - val validatedTargetKeyRing = KeyRingUtils.publicKeys(validatedTarget.keys) - val targetFingerprint = OpenPgpFingerprint.of(validatedTargetKeyRing).map() - val targetPrimaryKey = validatedTargetKeyRing.publicKey!! - val target = certSynopsisMap[targetFingerprint]!! - - // Direct-Key Signatures (delegations) by X on Y - val delegations = SignatureUtils.getDelegations(validatedTargetKeyRing) - for (delegation in delegations) { - processDelegation(targetPrimaryKey, target, delegation) - } - - // Certification Signatures by X on Y over user-ID U - val userIds = targetPrimaryKey.userIDs - while (userIds.hasNext()) { - val userId = userIds.next() - val userIdSigs = SignatureUtils.get3rdPartyCertificationsFor(userId, validatedTargetKeyRing) - processCertification(targetPrimaryKey, target, userId, userIdSigs) - } - } - - private fun processDelegation(targetPrimaryKey: PGPPublicKey, - target: CertSynopsis, - delegation: PGPSignature) { - val issuerCandidates = byKeyId[delegation.keyID] - ?: return - for (candidate in issuerCandidates) { - val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys) - val issuerFingerprint = OpenPgpFingerprint.of(issuerKeyRing).map() - val issuerSigningKey = issuerKeyRing.getPublicKey(delegation.keyID) - val issuer = certSynopsisMap[issuerFingerprint] - ?: continue - try { - val valid = SignatureVerifier.verifyDirectKeySignature(delegation, issuerSigningKey, - targetPrimaryKey, policy, referenceTime.timestamp) - if (valid) { - indexEdge(fromDelegation(issuer, target, delegation)) - } - } catch (e: SignatureValidationException) { - val targetFingerprint = OpenPgpFingerprint.of(targetPrimaryKey) - LOGGER.warn("Cannot verify signature by $issuerFingerprint on cert of $targetFingerprint", e) - } - } - } - - private fun processCertification(targetPrimaryKey: PGPPublicKey, - target: CertSynopsis, - userId: String, - userIdSigs: List) { - for (certification in userIdSigs) { - val issuerCandidates = byKeyId[certification.keyID] - ?: continue - for (candidate in issuerCandidates) { - val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys) - val issuerFingerprint = OpenPgpFingerprint.of(issuerKeyRing).map() - val issuerSigningKey = issuerKeyRing.getPublicKey(certification.keyID) - ?: continue - val issuer = certSynopsisMap[issuerFingerprint] - ?: continue - try { - val valid = SignatureVerifier.verifySignatureOverUserId(userId, certification, - issuerSigningKey, targetPrimaryKey, policy, referenceTime.timestamp) - if (valid) { - indexEdge(fromCertification(issuer, target, userId, certification)) - } - } catch (e: SignatureValidationException) { - LOGGER.warn("Cannot verify signature for '$userId' by $issuerFingerprint" + - " on cert of ${target.fingerprint}", e) - } - } - } - } - - private fun indexEdge(certification: Certification) { - // Index edge as outgoing edge for issuer - val issuer = certification.issuer.fingerprint - edges.getOrPut(issuer) { mutableListOf() }.also { indexOutEdge(it, certification) } - - // Index edge as incoming edge for target - val target = certification.target.fingerprint - reverseEdges.getOrPut(target) { mutableListOf() }.also { indexInEdge(it, certification) } - } - - private fun indexOutEdge(outEdges: MutableList, certification: Certification) { - val target = certification.target.fingerprint - for (outEdge in outEdges) { - if (target == outEdge.target.fingerprint) { - outEdge.add(certification) - return - } - } - outEdges.add(fromCertification(certification)) - } - - private fun indexInEdge(inEdges: MutableList, certification: Certification) { - val issuer = certification.issuer.fingerprint - for (inEdge in inEdges) { - if (issuer == inEdge.issuer.fingerprint) { - inEdge.add(certification) - return - } - } - inEdges.add(fromCertification(certification)) - } - - /** - * Return the constructed, initialized [Network]. - * - * @return finished network - */ - fun buildNetwork(): Network { - return Network(certSynopsisMap, edges, reverseEdges, referenceTime) - } + fun buildNetwork(): Network { + return networkBuilder.build() } } } diff --git a/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/AdHocTest.kt b/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/AdHocTest.kt index d7803112..176b5d48 100644 --- a/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/AdHocTest.kt +++ b/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/AdHocTest.kt @@ -8,7 +8,6 @@ class AdHocTest { @Test fun test() { val store = AdHocVectors.BestViaRoot().pgpCertificateStore - val wot = WebOfTrust(store).also { it.initialize() } - val network = wot.network + val network = WebOfTrust(store).buildNetwork() } } \ No newline at end of file diff --git a/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/WebOfTrustTest.kt b/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/WebOfTrustTest.kt index a606e227..b2917dfd 100644 --- a/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/WebOfTrustTest.kt +++ b/pgpainless-wot/src/test/kotlin/org/pgpainless/wot/WebOfTrustTest.kt @@ -33,9 +33,7 @@ class WebOfTrustTest { @Test fun testWithTwoNodesAndOneDelegation() { val certD = TestCertificateStores.oneDelegationGraph() - val wot = WebOfTrust(certD) - wot.initialize() - val network = wot.network + val network = WebOfTrust(certD).buildNetwork() assertEquals(2, network.nodes.size) assertHasEdge(network, fooBankAdmin, barBankCa) @@ -48,9 +46,7 @@ class WebOfTrustTest { @Test fun testWithCrossSignedCertificates() { val certD = TestCertificateStores.disconnectedGraph() - val wot = WebOfTrust(certD) - wot.initialize() - val network = wot.network + val network = WebOfTrust(certD).buildNetwork() assertEquals(5, network.nodes.size) assertTrue { @@ -81,9 +77,7 @@ class WebOfTrustTest { @Test fun testWotCreationOfEmptyCertificates() { val certD = TestCertificateStores.emptyGraph() - val wot = WebOfTrust(certD) - wot.initialize() - val network = wot.network + val network = WebOfTrust(certD).buildNetwork() assertTrue { network.nodes.isEmpty() } assertTrue { network.edges.isEmpty() } @@ -93,9 +87,7 @@ class WebOfTrustTest { @Test fun testWotWithAnomaly() { val store = TestCertificateStores.anomalyGraph() - val wot = WebOfTrust(store) - wot.initialize() - val network = wot.network + val network = WebOfTrust(store).buildNetwork() assertEquals(1, network.nodes.size) } diff --git a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationSetTest.kt b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationSetTest.kt index 8fa93605..f2a01619 100644 --- a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationSetTest.kt +++ b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationSetTest.kt @@ -9,14 +9,13 @@ import org.junit.jupiter.api.assertThrows import org.pgpainless.wot.dijkstra.sq.* import java.util.* import kotlin.test.assertEquals -import kotlin.test.assertFalse import kotlin.test.assertTrue class CertificationSetTest { - private val alice = CertSynopsis(Fingerprint("0000000000000000000000000000000000000000"), null, RevocationState.notRevoked(), mapOf()) - private val bob = CertSynopsis(Fingerprint("1111111111111111111111111111111111111111"), null, RevocationState.notRevoked(), mapOf()) - private val charlie = CertSynopsis(Fingerprint("2222222222222222222222222222222222222222"), null, RevocationState.notRevoked(), mapOf()) + private val alice = CertSynopsis(Fingerprint("A"), null, RevocationState.notRevoked(), mapOf()) + private val bob = CertSynopsis(Fingerprint("B"), null, RevocationState.notRevoked(), mapOf()) + private val charlie = CertSynopsis(Fingerprint("C"), null, RevocationState.notRevoked(), mapOf()) private val aliceSignsBob = Certification(alice, null, bob, Date()) private val aliceSignsBobUserId = Certification(alice, "Bob ", bob, Date()) @@ -105,8 +104,8 @@ class CertificationSetTest { val twoCerts = CertificationSet.fromCertification(aliceSignsBob) twoCerts.add(aliceSignsBobUserId) - assertEquals("0000000000000000000000000000000000000000 delegates to 1111111111111111111111111111111111111111\n" + - "0000000000000000000000000000000000000000 certifies [Bob ] 1111111111111111111111111111111111111111", twoCerts.toString()) + assertEquals("A certifies binding: null <-> B [120]\n" + + "A certifies binding: Bob <-> B [120]", twoCerts.toString()) } @Test diff --git a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationTest.kt b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationTest.kt index 3811f48a..3940366b 100644 --- a/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationTest.kt +++ b/wot-dijkstra/src/test/kotlin/org/pgpainless/wot/dijkstra/CertificationTest.kt @@ -11,17 +11,17 @@ import kotlin.test.assertEquals class CertificationTest { private val alice = CertSynopsis( - Fingerprint("0000000000000000000000000000000000000000"), + Fingerprint("A"), null, RevocationState.notRevoked(), mapOf(Pair("Alice ", RevocationState.notRevoked()))) private val bob = CertSynopsis( - Fingerprint("1111111111111111111111111111111111111111"), + Fingerprint("B"), null, RevocationState.notRevoked(), mapOf(Pair("Bob ", RevocationState.notRevoked()))) private val charlie = CertSynopsis( - Fingerprint("22222222222222222222222222222222222222222222"), + Fingerprint("C"), null, RevocationState.notRevoked(), mapOf()) @@ -29,21 +29,21 @@ class CertificationTest { @Test fun `verify result of toString() on certification`() { val certification = Certification(alice, "Bob ", bob, Date()) - assertEquals("0000000000000000000000000000000000000000 (Alice ) certifies [Bob ] 1111111111111111111111111111111111111111", + assertEquals("A certifies binding: Bob <-> B [120]", certification.toString()) } @Test fun `verify result of toString() on delegation`() { val delegation = Certification(alice, null, bob, Date()) - assertEquals("0000000000000000000000000000000000000000 (Alice ) delegates to 1111111111111111111111111111111111111111", + assertEquals("A certifies binding: null <-> B [120]", delegation.toString()) } @Test fun `verify result of toString() on delegation with userId-less issuer`() { val delegation = Certification(charlie, null, bob, Date()) - assertEquals("22222222222222222222222222222222222222222222 delegates to 1111111111111111111111111111111111111111", + assertEquals("C certifies binding: null <-> B [120]", delegation.toString()) } } \ No newline at end of file