1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-12-25 12:27:58 +01:00

Refactor WebOfTrust class

This commit is contained in:
Paul Schaub 2023-07-07 14:22:45 +02:00
parent 31a0151b55
commit 2e802c1f1e
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
5 changed files with 193 additions and 267 deletions

View file

@ -18,7 +18,6 @@ import org.pgpainless.signature.SignatureUtils
import org.pgpainless.signature.consumer.SignatureVerifier import org.pgpainless.signature.consumer.SignatureVerifier
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
import org.pgpainless.wot.dijkstra.sq.* 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.dijkstra.sq.ReferenceTime.Companion.now
import org.pgpainless.wot.util.CertificationFactory.Companion.fromCertification import org.pgpainless.wot.util.CertificationFactory.Companion.fromCertification
import org.pgpainless.wot.util.CertificationFactory.Companion.fromDelegation import org.pgpainless.wot.util.CertificationFactory.Companion.fromDelegation
@ -46,11 +45,10 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
*/ */
constructor(certificateDirectory: PGPCertificateDirectory): this(PGPCertificateStoreAdapter(certificateDirectory)) constructor(certificateDirectory: PGPCertificateDirectory): this(PGPCertificateStoreAdapter(certificateDirectory))
lateinit var network: Network fun buildNetwork(policy: Policy = PGPainless.getPolicy(), referenceTime: ReferenceTime = now()): Network {
fun initialize() {
val certificates = getAllCertificatesFromTheStore() val certificates = getAllCertificatesFromTheStore()
network = fromCertificates(certificates, PGPainless.getPolicy(), now()) val networkFactory = PGPNetworkFactory.fromCertificates(certificates, policy, referenceTime)
return networkFactory.buildNetwork()
} }
private fun getAllCertificatesFromTheStore(): Sequence<Certificate> { private fun getAllCertificatesFromTheStore(): Sequence<Certificate> {
@ -69,11 +67,21 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
return certificates return certificates
} }
/**
* Class for building the [Flow network][Network] from the given set of OpenPGP keys.
*/
private class PGPNetworkFactory private constructor(validatedCertificates: List<KeyRingInfo>,
private val policy: Policy,
private val referenceTime: ReferenceTime) {
companion object { companion object {
@JvmStatic
private val LOGGER = LoggerFactory.getLogger(PGPNetworkFactory::class.java)
@JvmStatic @JvmStatic
fun fromCertificates(certificates: Sequence<Certificate>, fun fromCertificates(certificates: Sequence<Certificate>,
policy: Policy, policy: Policy,
referenceTime: ReferenceTime): Network { referenceTime: ReferenceTime): PGPNetworkFactory {
return fromValidCertificates( return fromValidCertificates(
parseValidCertificates(certificates, policy, referenceTime), parseValidCertificates(certificates, policy, referenceTime),
policy, policy,
@ -84,9 +92,8 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
@JvmStatic @JvmStatic
fun fromValidCertificates(certificates: List<KeyRingInfo>, fun fromValidCertificates(certificates: List<KeyRingInfo>,
policy: Policy, policy: Policy,
referenceTime: ReferenceTime): Network { referenceTime: ReferenceTime): PGPNetworkFactory {
val nb = NetworkBuilder(certificates, policy, referenceTime) return PGPNetworkFactory(certificates, policy, referenceTime)
return nb.buildNetwork()
} }
@JvmStatic @JvmStatic
@ -94,50 +101,15 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
policy: Policy, policy: Policy,
referenceTime: ReferenceTime): List<KeyRingInfo> { referenceTime: ReferenceTime): List<KeyRingInfo> {
return certificates return certificates
.mapNotNull { cert -> .mapNotNull {
try { try { PGPainless.readKeyRing().publicKeyRing(it.inputStream) } catch (e: IOException) { null }
PGPainless.readKeyRing().publicKeyRing(cert.inputStream)
} catch (e: IOException) {
null
}
}
.map { cert ->
KeyRingInfo(cert, policy, referenceTime.timestamp)
} }
.map { KeyRingInfo(it, policy, referenceTime.timestamp) }
.toList() .toList()
} }
// Map signature to its revocation state
@JvmStatic
private fun revocationStateFromSignature(revocation: PGPSignature?): RevocationState {
if (revocation == null) {
return RevocationState.notRevoked()
}
val revocationReason = SignatureSubpacketsUtil.getRevocationReason(revocation)
?: return RevocationState.hardRevoked()
return if (RevocationAttributes.Reason.isHardRevocation(revocationReason.revocationReason))
RevocationState.hardRevoked()
else
RevocationState.softRevoked(revocation.creationTime)
} }
@JvmStatic private val networkBuilder: Network.Builder = Network.builder()
private fun OpenPgpFingerprint.map(): Fingerprint {
return Fingerprint(toString())
}
/**
* Class for building the [Flow network][Network] from the given set of OpenPGP keys.
*
*/
private class NetworkBuilder constructor(validatedCertificates: List<KeyRingInfo>,
private val policy: Policy,
private val referenceTime: ReferenceTime) {
companion object {
@JvmStatic
private val LOGGER = LoggerFactory.getLogger(NetworkBuilder::class.java)
}
// certificates keyed by fingerprint // certificates keyed by fingerprint
private val byFingerprint: MutableMap<Fingerprint, KeyRingInfo> = HashMap() private val byFingerprint: MutableMap<Fingerprint, KeyRingInfo> = HashMap()
@ -148,27 +120,15 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
// certificate synopses keyed by fingerprint // certificate synopses keyed by fingerprint
private val certSynopsisMap: MutableMap<Fingerprint, CertSynopsis> = HashMap() private val certSynopsisMap: MutableMap<Fingerprint, CertSynopsis> = HashMap()
// Issuer -> Targets, edges keyed by issuer
private val edges: MutableMap<Fingerprint, MutableList<CertificationSet>> = HashMap()
// Target -> Issuers, edges keyed by target
private val reverseEdges: MutableMap<Fingerprint, MutableList<CertificationSet>> = HashMap()
init { init {
synopsizeCertificates(validatedCertificates) validatedCertificates.forEach { indexAsNode(it) }
findEdges(validatedCertificates) validatedCertificates.forEach { findEdgesWithTarget(it) }
} }
private fun synopsizeCertificates(validatedCertificates: List<KeyRingInfo>) { private fun indexAsNode(cert: KeyRingInfo) {
for (cert in validatedCertificates) {
synopsize(cert)
}
}
private fun synopsize(cert: KeyRingInfo) {
// index by fingerprint // index by fingerprint
val certFingerprint = cert.fingerprint.map() val certFingerprint = Fingerprint(cert.fingerprint)
if (!byFingerprint.containsKey(certFingerprint)) { if (!byFingerprint.containsKey(certFingerprint)) {
byFingerprint[certFingerprint] = cert byFingerprint[certFingerprint] = cert
} }
@ -186,7 +146,7 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
certsWithKey.add(cert) certsWithKey.add(cert)
val userIds: MutableMap<String, RevocationState> = HashMap() val userIds: MutableMap<String, RevocationState> = HashMap()
for (userId in cert.userIds) { for (userId in cert.userIds) {
val state: RevocationState = revocationStateFromSignature(cert.getUserIdRevocation(userId)) val state = RevocationState(cert.getUserIdRevocation(userId))
userIds[userId] = state userIds[userId] = state
} }
@ -197,23 +157,17 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
// Some keys are malformed and have no KeyFlags // Some keys are malformed and have no KeyFlags
return return
} }
certSynopsisMap[certFingerprint] = CertSynopsis(certFingerprint, val node = CertSynopsis(certFingerprint,
expirationDate, expirationDate,
revocationStateFromSignature(cert.revocationSelfSignature), RevocationState(cert.revocationSelfSignature),
userIds) userIds)
} certSynopsisMap[certFingerprint] = node
networkBuilder.addNode(node)
private fun findEdges(validatedCertificates: List<KeyRingInfo>) {
// Identify certifications and delegations
// Target = cert carrying a signature
for (validatedTarget in validatedCertificates) {
findEdgesWithTarget(validatedTarget)
}
} }
private fun findEdgesWithTarget(validatedTarget: KeyRingInfo) { private fun findEdgesWithTarget(validatedTarget: KeyRingInfo) {
val validatedTargetKeyRing = KeyRingUtils.publicKeys(validatedTarget.keys) val validatedTargetKeyRing = KeyRingUtils.publicKeys(validatedTarget.keys)
val targetFingerprint = OpenPgpFingerprint.of(validatedTargetKeyRing).map() val targetFingerprint = Fingerprint(OpenPgpFingerprint.of(validatedTargetKeyRing))
val targetPrimaryKey = validatedTargetKeyRing.publicKey!! val targetPrimaryKey = validatedTargetKeyRing.publicKey!!
val target = certSynopsisMap[targetFingerprint]!! val target = certSynopsisMap[targetFingerprint]!!
@ -228,7 +182,10 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
while (userIds.hasNext()) { while (userIds.hasNext()) {
val userId = userIds.next() val userId = userIds.next()
val userIdSigs = SignatureUtils.get3rdPartyCertificationsFor(userId, validatedTargetKeyRing) val userIdSigs = SignatureUtils.get3rdPartyCertificationsFor(userId, validatedTargetKeyRing)
processCertification(targetPrimaryKey, target, userId, userIdSigs) userIdSigs.forEach {
processCertificationOnUserId(targetPrimaryKey, target, userId, it)
}
} }
} }
@ -239,7 +196,7 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
?: return ?: return
for (candidate in issuerCandidates) { for (candidate in issuerCandidates) {
val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys) val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys)
val issuerFingerprint = OpenPgpFingerprint.of(issuerKeyRing).map() val issuerFingerprint = Fingerprint(OpenPgpFingerprint.of(issuerKeyRing))
val issuerSigningKey = issuerKeyRing.getPublicKey(delegation.keyID) val issuerSigningKey = issuerKeyRing.getPublicKey(delegation.keyID)
val issuer = certSynopsisMap[issuerFingerprint] val issuer = certSynopsisMap[issuerFingerprint]
?: continue ?: continue
@ -247,7 +204,7 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
val valid = SignatureVerifier.verifyDirectKeySignature(delegation, issuerSigningKey, val valid = SignatureVerifier.verifyDirectKeySignature(delegation, issuerSigningKey,
targetPrimaryKey, policy, referenceTime.timestamp) targetPrimaryKey, policy, referenceTime.timestamp)
if (valid) { if (valid) {
indexEdge(fromDelegation(issuer, target, delegation)) networkBuilder.addEdge(fromDelegation(issuer, target, delegation))
} }
} catch (e: SignatureValidationException) { } catch (e: SignatureValidationException) {
val targetFingerprint = OpenPgpFingerprint.of(targetPrimaryKey) val targetFingerprint = OpenPgpFingerprint.of(targetPrimaryKey)
@ -256,16 +213,15 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
} }
} }
private fun processCertification(targetPrimaryKey: PGPPublicKey, private fun processCertificationOnUserId(targetPrimaryKey: PGPPublicKey,
target: CertSynopsis, target: CertSynopsis,
userId: String, userId: String,
userIdSigs: List<PGPSignature>) { certification: PGPSignature) {
for (certification in userIdSigs) {
val issuerCandidates = byKeyId[certification.keyID] val issuerCandidates = byKeyId[certification.keyID]
?: continue ?: return
for (candidate in issuerCandidates) { for (candidate in issuerCandidates) {
val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys) val issuerKeyRing = KeyRingUtils.publicKeys(candidate.keys)
val issuerFingerprint = OpenPgpFingerprint.of(issuerKeyRing).map() val issuerFingerprint = Fingerprint(OpenPgpFingerprint.of(issuerKeyRing))
val issuerSigningKey = issuerKeyRing.getPublicKey(certification.keyID) val issuerSigningKey = issuerKeyRing.getPublicKey(certification.keyID)
?: continue ?: continue
val issuer = certSynopsisMap[issuerFingerprint] val issuer = certSynopsisMap[issuerFingerprint]
@ -274,7 +230,7 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
val valid = SignatureVerifier.verifySignatureOverUserId(userId, certification, val valid = SignatureVerifier.verifySignatureOverUserId(userId, certification,
issuerSigningKey, targetPrimaryKey, policy, referenceTime.timestamp) issuerSigningKey, targetPrimaryKey, policy, referenceTime.timestamp)
if (valid) { if (valid) {
indexEdge(fromCertification(issuer, target, userId, certification)) networkBuilder.addEdge(fromCertification(issuer, target, userId, certification))
} }
} catch (e: SignatureValidationException) { } catch (e: SignatureValidationException) {
LOGGER.warn("Cannot verify signature for '$userId' by $issuerFingerprint" + LOGGER.warn("Cannot verify signature for '$userId' by $issuerFingerprint" +
@ -282,38 +238,19 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
} }
} }
} }
}
private fun indexEdge(certification: Certification) { private fun Fingerprint(fingerprint: OpenPgpFingerprint) = Fingerprint(fingerprint.toString())
// 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 private fun RevocationState(revocation: PGPSignature?): RevocationState {
val target = certification.target.fingerprint if (revocation == null) {
reverseEdges.getOrPut(target) { mutableListOf() }.also { indexInEdge(it, certification) } return RevocationState.notRevoked()
} }
val revocationReason = SignatureSubpacketsUtil.getRevocationReason(revocation)
private fun indexOutEdge(outEdges: MutableList<CertificationSet>, certification: Certification) { ?: return RevocationState.hardRevoked()
val target = certification.target.fingerprint return if (RevocationAttributes.Reason.isHardRevocation(revocationReason.revocationReason))
for (outEdge in outEdges) { RevocationState.hardRevoked()
if (target == outEdge.target.fingerprint) { else
outEdge.add(certification) RevocationState.softRevoked(revocation.creationTime)
return
}
}
outEdges.add(fromCertification(certification))
}
private fun indexInEdge(inEdges: MutableList<CertificationSet>, certification: Certification) {
val issuer = certification.issuer.fingerprint
for (inEdge in inEdges) {
if (issuer == inEdge.issuer.fingerprint) {
inEdge.add(certification)
return
}
}
inEdges.add(fromCertification(certification))
} }
/** /**
@ -322,8 +259,7 @@ class WebOfTrust(private val certificateStore: PGPCertificateStore) {
* @return finished network * @return finished network
*/ */
fun buildNetwork(): Network { fun buildNetwork(): Network {
return Network(certSynopsisMap, edges, reverseEdges, referenceTime) return networkBuilder.build()
}
} }
} }
} }

View file

@ -8,7 +8,6 @@ class AdHocTest {
@Test @Test
fun test() { fun test() {
val store = AdHocVectors.BestViaRoot().pgpCertificateStore val store = AdHocVectors.BestViaRoot().pgpCertificateStore
val wot = WebOfTrust(store).also { it.initialize() } val network = WebOfTrust(store).buildNetwork()
val network = wot.network
} }
} }

View file

@ -33,9 +33,7 @@ class WebOfTrustTest {
@Test @Test
fun testWithTwoNodesAndOneDelegation() { fun testWithTwoNodesAndOneDelegation() {
val certD = TestCertificateStores.oneDelegationGraph() val certD = TestCertificateStores.oneDelegationGraph()
val wot = WebOfTrust(certD) val network = WebOfTrust(certD).buildNetwork()
wot.initialize()
val network = wot.network
assertEquals(2, network.nodes.size) assertEquals(2, network.nodes.size)
assertHasEdge(network, fooBankAdmin, barBankCa) assertHasEdge(network, fooBankAdmin, barBankCa)
@ -48,9 +46,7 @@ class WebOfTrustTest {
@Test @Test
fun testWithCrossSignedCertificates() { fun testWithCrossSignedCertificates() {
val certD = TestCertificateStores.disconnectedGraph() val certD = TestCertificateStores.disconnectedGraph()
val wot = WebOfTrust(certD) val network = WebOfTrust(certD).buildNetwork()
wot.initialize()
val network = wot.network
assertEquals(5, network.nodes.size) assertEquals(5, network.nodes.size)
assertTrue { assertTrue {
@ -81,9 +77,7 @@ class WebOfTrustTest {
@Test @Test
fun testWotCreationOfEmptyCertificates() { fun testWotCreationOfEmptyCertificates() {
val certD = TestCertificateStores.emptyGraph() val certD = TestCertificateStores.emptyGraph()
val wot = WebOfTrust(certD) val network = WebOfTrust(certD).buildNetwork()
wot.initialize()
val network = wot.network
assertTrue { network.nodes.isEmpty() } assertTrue { network.nodes.isEmpty() }
assertTrue { network.edges.isEmpty() } assertTrue { network.edges.isEmpty() }
@ -93,9 +87,7 @@ class WebOfTrustTest {
@Test @Test
fun testWotWithAnomaly() { fun testWotWithAnomaly() {
val store = TestCertificateStores.anomalyGraph() val store = TestCertificateStores.anomalyGraph()
val wot = WebOfTrust(store) val network = WebOfTrust(store).buildNetwork()
wot.initialize()
val network = wot.network
assertEquals(1, network.nodes.size) assertEquals(1, network.nodes.size)
} }

View file

@ -9,14 +9,13 @@ import org.junit.jupiter.api.assertThrows
import org.pgpainless.wot.dijkstra.sq.* import org.pgpainless.wot.dijkstra.sq.*
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class CertificationSetTest { class CertificationSetTest {
private val alice = CertSynopsis(Fingerprint("0000000000000000000000000000000000000000"), null, RevocationState.notRevoked(), mapOf()) private val alice = CertSynopsis(Fingerprint("A"), null, RevocationState.notRevoked(), mapOf())
private val bob = CertSynopsis(Fingerprint("1111111111111111111111111111111111111111"), null, RevocationState.notRevoked(), mapOf()) private val bob = CertSynopsis(Fingerprint("B"), null, RevocationState.notRevoked(), mapOf())
private val charlie = CertSynopsis(Fingerprint("2222222222222222222222222222222222222222"), null, RevocationState.notRevoked(), mapOf()) private val charlie = CertSynopsis(Fingerprint("C"), null, RevocationState.notRevoked(), mapOf())
private val aliceSignsBob = Certification(alice, null, bob, Date()) private val aliceSignsBob = Certification(alice, null, bob, Date())
private val aliceSignsBobUserId = Certification(alice, "Bob <bob@example.org>", bob, Date()) private val aliceSignsBobUserId = Certification(alice, "Bob <bob@example.org>", bob, Date())
@ -105,8 +104,8 @@ class CertificationSetTest {
val twoCerts = CertificationSet.fromCertification(aliceSignsBob) val twoCerts = CertificationSet.fromCertification(aliceSignsBob)
twoCerts.add(aliceSignsBobUserId) twoCerts.add(aliceSignsBobUserId)
assertEquals("0000000000000000000000000000000000000000 delegates to 1111111111111111111111111111111111111111\n" + assertEquals("A certifies binding: null <-> B [120]\n" +
"0000000000000000000000000000000000000000 certifies [Bob <bob@example.org>] 1111111111111111111111111111111111111111", twoCerts.toString()) "A certifies binding: Bob <bob@example.org> <-> B [120]", twoCerts.toString())
} }
@Test @Test

View file

@ -11,17 +11,17 @@ import kotlin.test.assertEquals
class CertificationTest { class CertificationTest {
private val alice = CertSynopsis( private val alice = CertSynopsis(
Fingerprint("0000000000000000000000000000000000000000"), Fingerprint("A"),
null, null,
RevocationState.notRevoked(), RevocationState.notRevoked(),
mapOf(Pair("Alice <alice@pgpainless.org>", RevocationState.notRevoked()))) mapOf(Pair("Alice <alice@pgpainless.org>", RevocationState.notRevoked())))
private val bob = CertSynopsis( private val bob = CertSynopsis(
Fingerprint("1111111111111111111111111111111111111111"), Fingerprint("B"),
null, null,
RevocationState.notRevoked(), RevocationState.notRevoked(),
mapOf(Pair("Bob <bob@example.org>", RevocationState.notRevoked()))) mapOf(Pair("Bob <bob@example.org>", RevocationState.notRevoked())))
private val charlie = CertSynopsis( private val charlie = CertSynopsis(
Fingerprint("22222222222222222222222222222222222222222222"), Fingerprint("C"),
null, null,
RevocationState.notRevoked(), RevocationState.notRevoked(),
mapOf()) mapOf())
@ -29,21 +29,21 @@ class CertificationTest {
@Test @Test
fun `verify result of toString() on certification`() { fun `verify result of toString() on certification`() {
val certification = Certification(alice, "Bob <bob@example.org>", bob, Date()) val certification = Certification(alice, "Bob <bob@example.org>", bob, Date())
assertEquals("0000000000000000000000000000000000000000 (Alice <alice@pgpainless.org>) certifies [Bob <bob@example.org>] 1111111111111111111111111111111111111111", assertEquals("A certifies binding: Bob <bob@example.org> <-> B [120]",
certification.toString()) certification.toString())
} }
@Test @Test
fun `verify result of toString() on delegation`() { fun `verify result of toString() on delegation`() {
val delegation = Certification(alice, null, bob, Date()) val delegation = Certification(alice, null, bob, Date())
assertEquals("0000000000000000000000000000000000000000 (Alice <alice@pgpainless.org>) delegates to 1111111111111111111111111111111111111111", assertEquals("A certifies binding: null <-> B [120]",
delegation.toString()) delegation.toString())
} }
@Test @Test
fun `verify result of toString() on delegation with userId-less issuer`() { fun `verify result of toString() on delegation with userId-less issuer`() {
val delegation = Certification(charlie, null, bob, Date()) val delegation = Certification(charlie, null, bob, Date())
assertEquals("22222222222222222222222222222222222222222222 delegates to 1111111111111111111111111111111111111111", assertEquals("C certifies binding: null <-> B [120]",
delegation.toString()) delegation.toString())
} }
} }