Skip to content

Commit 3de38dc

Browse files
committed
checksum-dependency: use full pgp fingerprints for verification
1 parent 9ae7fdd commit 3de38dc

File tree

12 files changed

+358
-96
lines changed

12 files changed

+358
-96
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ This library is distributed under terms of Apache License 2.0
8787

8888
Change log
8989
----------
90+
v1.86
91+
* checksum-dependency: use full fingerprint for PGP verification
92+
9093
v1.85
9194
* licence-gather: better support for build cache by adding PathSensitivity
9295
* checksum-dependency: cache PGP public keys under `%{ROOT_DIR}/gradle/checksum-dependency-plugin/cached-pgp-keys`

plugins/checksum-dependency-plugin/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,9 @@ Verification options
499499

500500
Changelog
501501
---------
502+
v1.86
503+
* Use full fingerprint for PGP verification
504+
502505
v1.85
503506
* Cache public PGP keys under `%{ROOT_DIR}/gradle/checksum-dependency-plugin/cached-pgp-keys` directory
504507
* Bump org.bouncycastle:bcpg-jdk15on to 1.70

plugins/checksum-dependency-plugin/src/main/kotlin/com/github/vlsi/gradle/checksum/ChecksumDependency.kt

+34-22
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,9 @@ class ChecksumDependency(
219219
val signatures = art.file.toSignatureList()
220220
keysToVerify[art] = signatures
221221
for (sign in signatures) {
222-
if (verificationDb.isIgnored(sign.keyID)) {
223-
logger.debug("Public key ${sign.keyID.hexKey} is ignored via <ignored-keys>, so ${art.id.artifactDependency} is assumed to be not signed with that key")
222+
val signKey = sign.pgpShortKeyId
223+
if (verificationDb.isIgnored(signKey)) {
224+
logger.debug("Public key $signKey is ignored via <ignored-keys>, so ${art.id.artifactDependency} is assumed to be not signed with that key")
224225
continue
225226
}
226227
}
@@ -235,31 +236,35 @@ class ChecksumDependency(
235236
logger.debug { "Resolved signature $signatureDependency" }
236237
receivedSignatures.add(signatureDependency)
237238
for (sign in art.file.toSignatureList()) {
238-
if (verificationDb.isIgnored(sign.keyID)) {
239-
logger.debug("Public key ${sign.keyID.hexKey} is ignored via <ignored-keys>, so ${art.id.artifactDependency} is assumed to be not signed with that key")
239+
val signKey = sign.pgpShortKeyId
240+
if (verificationDb.isIgnored(signKey)) {
241+
logger.debug("Public key $signKey is ignored via <ignored-keys>, so ${art.id.artifactDependency} is assumed to be not signed with that key")
240242
continue
241243
}
242244
val verifySignature = keyStore
243-
.getKeyAsync(sign.keyID, signatureDependency, executors)
244-
.thenAcceptAsync({ publicKey ->
245-
if (publicKey == null) {
246-
logger.warn("Public key ${sign.keyID.hexKey} is not found. The key was used to sign ${art.id.artifactDependency}." +
245+
.getKeyAsync(signKey, signatureDependency, executors)
246+
.thenAcceptAsync({ publicKeys ->
247+
if (publicKeys.isEmpty()) {
248+
logger.warn("Public key $signKey is not found. The key was used to sign ${art.id.artifactDependency}." +
247249
" Please ask dependency author to publish the PGP key otherwise signature verification is not possibles")
248-
verificationDb.ignoreKey(sign.keyID)
250+
verificationDb.ignoreKey(signKey)
249251
return@thenAcceptAsync
250252
}
251-
logger.debug { "Verifying signature ${sign.keyID.hexKey} for ${art.id.artifactDependency}" }
252-
val file = originalFiles[dependencyChecksum.id]!!
253-
val validSignature = signatureVerificationTimer(file.length()) {
254-
verifySignature(file, sign, publicKey)
255-
}
256-
if (validSignature) {
257-
synchronized(dependencyChecksum) {
258-
dependencyChecksum.pgpKeys += sign.keyID
253+
for (publicKey in publicKeys) {
254+
val fullKeyId = publicKey.pgpFullKeyId
255+
logger.debug { "Verifying signature $fullKeyId for ${art.id.artifactDependency}" }
256+
val file = originalFiles[dependencyChecksum.id]!!
257+
val validSignature = signatureVerificationTimer(file.length()) {
258+
verifySignature(file, sign, publicKey)
259+
}
260+
if (validSignature) {
261+
synchronized(dependencyChecksum) {
262+
dependencyChecksum.pgpKeys += fullKeyId
263+
}
264+
}
265+
logger.log(if (validSignature) LogLevel.DEBUG else LogLevel.LIFECYCLE) {
266+
"${if (validSignature) "OK" else "KO"}: verification of ${art.id.artifactDependency} via $fullKeyId"
259267
}
260-
}
261-
logger.log(if (validSignature) LogLevel.DEBUG else LogLevel.LIFECYCLE) {
262-
"${if (validSignature) "OK" else "KO"}: verification of ${art.id.artifactDependency} via ${publicKey.keyID.hexKey}"
263268
}
264269
}, executors.cpu)
265270
verifyPgpTasks.add(verifySignature)
@@ -326,7 +331,14 @@ class ChecksumDependency(
326331
}
327332

328333
private fun verifySignature(file: File, sign: PGPSignature, publicKey: PGPPublicKey): Boolean {
329-
sign.init(BcPGPContentVerifierBuilderProvider(), publicKey)
334+
try {
335+
sign.init(BcPGPContentVerifierBuilderProvider(), publicKey)
336+
} catch (e: Throwable) {
337+
e.addSuppressed(
338+
Throwable("Verifying $file with key ${publicKey.pgpFullKeyId}, sign ${sign.pgpShortKeyId}")
339+
)
340+
throw e
341+
}
330342
file.forEachBlock { block, size -> sign.update(block, 0, size) }
331343
return sign.verify()
332344
}
@@ -343,7 +355,7 @@ class ChecksumDependency(
343355
append(" ").append(violation).appendPlatformLine(":")
344356
artifacts
345357
.asSequence()
346-
.map { "${it.id.dependencyNotation} (pgp=${it.pgpKeys.hexKeys}, sha512=${it.sha512.ifEmpty { "[computation skipped]" }})" }
358+
.map { "${it.id.dependencyNotation} (pgp=${it.pgpKeys}, sha512=${it.sha512.ifEmpty { "[computation skipped]" }})" }
347359
.sorted()
348360
.forEach {
349361
append(" ").appendPlatformLine(it)

plugins/checksum-dependency-plugin/src/main/kotlin/com/github/vlsi/gradle/checksum/ChecksumDependencyPlugin.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ open class ChecksumDependencyPlugin : Plugin<Settings> {
7373
settings.property("checksum.xml", "checksum.xml")
7474
val checksums = File(settings.rootDir, checksumFileName)
7575
val buildDir = settings.property("checksumBuildDir", "build/checksum")
76+
val cachedKeysTempRoot =
77+
File(
78+
settings.rootDir,
79+
settings.property("checksumCachedPgpKeysTempDir", "build/checksum/key-cache-temp")
80+
)
7681
val buildFolder = File(settings.rootDir, buildDir)
7782
val cachedKeysRoot =
7883
settings.property("checksumCachedPgpKeysDir", "%{ROOT_DIR}/gradle/checksum-dependency-plugin/cached-pgp-keys")
@@ -137,7 +142,7 @@ open class ChecksumDependencyPlugin : Plugin<Settings> {
137142
readTimeout = Duration.ofSeconds(pgpReadTimeout)
138143
)
139144
)
140-
val keyStore = KeyStore(cachedKeysRoot, keyDownloader)
145+
val keyStore = KeyStore(cachedKeysRoot, cachedKeysTempRoot, keyDownloader)
141146
val verification =
142147
if (checksums.exists()) {
143148
DependencyVerificationStore.load(checksums)

plugins/checksum-dependency-plugin/src/main/kotlin/com/github/vlsi/gradle/checksum/FileExtensions.kt

+1
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ internal fun File.sha512(): String {
4343
md.update(buffer, 0, bytesRead)
4444
}
4545
return BigInteger(1, md.digest()).toString(16).toUpperCase()
46+
.padStart(128, '0')
4647
}

plugins/checksum-dependency-plugin/src/main/kotlin/com/github/vlsi/gradle/checksum/SignatureExtensions.kt

+52-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
*/
1717
package com.github.vlsi.gradle.checksum
1818

19+
import com.github.vlsi.gradle.checksum.pgp.PgpKeyId
20+
import org.bouncycastle.bcpg.ArmoredOutputStream
1921
import java.io.File
2022
import java.io.InputStream
2123
import org.bouncycastle.openpgp.*
2224
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
2325
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator
26+
import java.io.ByteArrayOutputStream
27+
import java.io.OutputStream
28+
import java.nio.ByteBuffer
2429

2530
fun InputStream.toSignatureList() =
2631
buffered()
@@ -38,10 +43,56 @@ fun File.toSignatureList() = inputStream().toSignatureList()
3843

3944
val PGPSignature.hexKey: String get() = keyID.hexKey
4045

41-
val Iterable<Long>.hexKeys: String get() = sorted().joinToString(prefix = "[", postfix = "]") { it.hexKey }
46+
val PGPSignature.pgpShortKeyId: PgpKeyId.Short get() =
47+
PgpKeyId.Short(ByteBuffer.allocate(8).putLong(keyID).array())
48+
49+
val PGPPublicKey.pgpFullKeyId: PgpKeyId.Full get() =
50+
PgpKeyId.Full(fingerprint)
51+
52+
val PGPPublicKey.pgpShortKeyId: PgpKeyId.Short get() =
53+
PgpKeyId.Short(ByteBuffer.allocate(8).putLong(keyID).array())
4254

4355
// `java.lang`.Long.toHexString(this) does not generate leading 0
4456
val Long.hexKey: String get() = "%016x".format(this)
4557

4658
fun InputStream.readPgpPublicKeys() =
4759
PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(this), BcKeyFingerprintCalculator())
60+
61+
fun PGPPublicKeyRingCollection.publicKeysWithId(keyId: PgpKeyId.Short) =
62+
keyRings.asSequence()
63+
.flatMap { keyRing ->
64+
keyRing.asSequence().filter { it.keyID == keyId.keyId }
65+
}
66+
67+
/**
68+
* Remove all UserIDs and Signatures to avoid storing personally identifiable information.
69+
*/
70+
fun PGPPublicKeyRingCollection.strip() =
71+
PGPPublicKeyRingCollection(
72+
toList()
73+
.map { pgpPublicKeyRing ->
74+
PGPPublicKeyRing(
75+
pgpPublicKeyRing
76+
.map {
77+
it.signatures.asSequence()
78+
.fold(it) { key, signature ->
79+
PGPPublicKey.removeCertification(key, signature)
80+
}
81+
}
82+
.map {
83+
it.rawUserIDs.asSequence()
84+
.fold(it) { key, userId ->
85+
PGPPublicKey.removeCertification(key, userId)
86+
}
87+
}
88+
)
89+
}
90+
)
91+
92+
fun armourEncode(body: (OutputStream) -> Unit)=
93+
ByteArrayOutputStream().apply {
94+
ArmoredOutputStream(this).use {
95+
it.clearHeaders()
96+
body(it)
97+
}
98+
}.toByteArray()

plugins/checksum-dependency-plugin/src/main/kotlin/com/github/vlsi/gradle/checksum/model/DependencyVerification.kt

+17-17
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package com.github.vlsi.gradle.checksum.model
1818

1919
import com.github.vlsi.gradle.checksum.debug
20-
import com.github.vlsi.gradle.checksum.hexKeys
20+
import com.github.vlsi.gradle.checksum.pgp.PgpKeyId
2121
import org.gradle.api.artifacts.DependencyArtifact
2222
import org.gradle.api.logging.Logging
2323

@@ -56,7 +56,7 @@ class DependencyChecksum(
5656
val id: Id
5757
) {
5858
val sha512 = mutableSetOf<String>()
59-
val pgpKeys = mutableSetOf<Long>()
59+
val pgpKeys = mutableSetOf<PgpKeyId.Full>()
6060
val verificationConfig: VerificationConfig
6161
get() =
6262
VerificationConfig(
@@ -71,19 +71,19 @@ class DependencyChecksum(
7171
}
7272

7373
override fun toString(): String {
74-
return "DependencyChecksum(sha512=$sha512, pgpKeys=${pgpKeys.hexKeys}"
74+
return "DependencyChecksum(sha512=$sha512, pgpKeys=$pgpKeys"
7575
}
7676
}
7777

7878
class DependencyVerification(val defaultVerificationConfig: VerificationConfig) {
79-
val ignoredKeys = mutableSetOf<Long>()
79+
val ignoredKeys = mutableSetOf<PgpKeyId>()
8080

81-
val groupKeys = mutableMapOf<String, MutableSet<Long>>()
81+
val groupKeys = mutableMapOf<String, MutableSet<PgpKeyId.Full>>()
8282

83-
fun add(group: String, key: Long): Boolean =
83+
fun add(group: String, key: PgpKeyId.Full): Boolean =
8484
groupKeys.getOrPut(group) { mutableSetOf() }.add(key)
8585

86-
fun groupKeys(group: String): Set<Long>? = groupKeys[group]
86+
fun groupKeys(group: String): Set<PgpKeyId.Full>? = groupKeys[group]
8787

8888
val dependencies = mutableMapOf<Id, DependencyChecksum>()
8989

@@ -100,7 +100,7 @@ class DependencyVerification(val defaultVerificationConfig: VerificationConfig)
100100
}
101101

102102
override fun toString(): String {
103-
return "DependencyVerification(ignoredKeys=${ignoredKeys.hexKeys}, trustedKeys=${groupKeys.mapValues { it.value.hexKeys }}, dependencies=$dependencies)"
103+
return "DependencyVerification(ignoredKeys=$ignoredKeys, trustedKeys=${groupKeys.mapValues { it.value.toString() }}, dependencies=$dependencies)"
104104
}
105105
}
106106

@@ -123,9 +123,9 @@ class DependencyVerificationDb(
123123
fun getConfigFor(id: Id): VerificationConfig =
124124
verification.dependencies[id]?.verificationConfig ?: verification.defaultVerificationConfig
125125

126-
fun isIgnored(key: Long) = verification.ignoredKeys.contains(key)
126+
fun isIgnored(key: PgpKeyId) = verification.ignoredKeys.contains(key)
127127

128-
fun ignoreKey(key: Long) {
128+
fun ignoreKey(key: PgpKeyId) {
129129
updatedVerification.ignoredKeys += key
130130
hasUpdates = true
131131
}
@@ -153,18 +153,18 @@ class DependencyVerificationDb(
153153
val pass = groupKeys.any { dependencyChecksum.pgpKeys.contains(it) }
154154
logger.debug {
155155
"${if (pass) "OK" else "KO"} PGP group verification for $id." +
156-
" The file was signed via ${dependencyChecksum.pgpKeys.hexKeys}," +
157-
" trusted keys for group ${id.group} are ${groupKeys.hexKeys}"
156+
" The file was signed via ${dependencyChecksum.pgpKeys}," +
157+
" trusted keys for group ${id.group} are $groupKeys"
158158
}
159159
if (pass) {
160160
pgpResult = PgpLevel.GROUP
161161
} else if (expected == null && verificationConfig.pgp == PgpLevel.GROUP) {
162162
details +=
163-
"Trusted PGP keys for group ${id.group} are ${groupKeys.hexKeys}, " +
163+
"Trusted PGP keys for group ${id.group} are $groupKeys, " +
164164
if (dependencyChecksum.pgpKeys.isEmpty()) {
165165
"however no signature found"
166166
} else {
167-
"however artifact is signed by ${dependencyChecksum.pgpKeys.hexKeys} only"
167+
"however artifact is signed by ${dependencyChecksum.pgpKeys} only"
168168
}
169169
}
170170
}
@@ -186,13 +186,13 @@ class DependencyVerificationDb(
186186
val pass = expected.pgpKeys.any { dependencyChecksum.pgpKeys.contains(it) }
187187
logger.debug {
188188
"${if (pass) "OK" else "KO"} PGP module verification for $id." +
189-
" The file was signed via ${dependencyChecksum.pgpKeys.hexKeys}," +
190-
" trusted keys for module are ${expected.pgpKeys.hexKeys}"
189+
" The file was signed via ${dependencyChecksum.pgpKeys}," +
190+
" trusted keys for module are ${expected.pgpKeys}"
191191
}
192192
if (pass) {
193193
pgpResult = PgpLevel.MODULE
194194
} else {
195-
details += "Expecting one of the following PGP signatures: ${expected.pgpKeys.hexKeys}, but artifact is signed by ${dependencyChecksum.pgpKeys.hexKeys} only"
195+
details += "Expecting one of the following PGP signatures: ${expected.pgpKeys}, but artifact is signed by ${dependencyChecksum.pgpKeys} only"
196196
}
197197
}
198198
if (expected.sha512.isNotEmpty()) {

0 commit comments

Comments
 (0)