• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pgpainless / pgpainless / #1074

20 Jan 2026 11:18PM UTC coverage: 85.293% (+0.09%) from 85.206%
#1074

push

github

vanitasvitae
Bump sop-java to 15.0.0

6774 of 7942 relevant lines covered (85.29%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.33
/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/OpenPGPKeyUpdater.kt
1
// SPDX-FileCopyrightText: 2025 Paul Schaub <info@pgpainless.org>
2
//
3
// SPDX-License-Identifier: Apache-2.0
4

5
package org.pgpainless.key.modification.secretkeyring
6

7
import java.util.*
8
import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites.Combination
9
import org.bouncycastle.openpgp.api.KeyPairGeneratorCallback
10
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
11
import org.bouncycastle.openpgp.api.OpenPGPKey
12
import org.bouncycastle.openpgp.api.OpenPGPKeyEditor
13
import org.bouncycastle.openpgp.api.SignatureParameters
14
import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator
15
import org.pgpainless.PGPainless
16
import org.pgpainless.algorithm.Feature
17
import org.pgpainless.bouncycastle.PolicyAdapter
18
import org.pgpainless.bouncycastle.extensions.getKeyVersion
19
import org.pgpainless.bouncycastle.extensions.toAEADCipherModes
20
import org.pgpainless.bouncycastle.extensions.toCompressionAlgorithms
21
import org.pgpainless.bouncycastle.extensions.toHashAlgorithms
22
import org.pgpainless.bouncycastle.extensions.toSymmetricKeyAlgorithms
23
import org.pgpainless.key.protection.SecretKeyRingProtector
24
import org.pgpainless.policy.Policy
25

26
class OpenPGPKeyUpdater(
1✔
27
    private var key: OpenPGPKey,
1✔
28
    private val protector: SecretKeyRingProtector,
1✔
29
    private val api: PGPainless = PGPainless.getInstance(),
1✔
30
    private val policy: Policy = api.algorithmPolicy,
1✔
31
    private val referenceTime: Date = Date()
1✔
32
) {
33

34
    init {
1✔
35
        key =
1✔
36
            OpenPGPKey(
1✔
37
                key.pgpSecretKeyRing, api.implementation, PolicyAdapter(Policy.wildcardPolicy()))
1✔
38
    }
1✔
39

40
    private val keyEditor = OpenPGPKeyEditor(key, protector)
1✔
41

42
    fun extendExpirationIfExpiresBefore(
×
43
        expiresBeforeSeconds: Long,
44
        newExpirationTimeSecondsFromNow: Long? = _5YEARS
×
45
    ) = apply {
×
46
        require(expiresBeforeSeconds > 0) {
×
47
            "Time period to check expiration within MUST be positive."
×
48
        }
49
        require(newExpirationTimeSecondsFromNow == null || newExpirationTimeSecondsFromNow > 0) {
×
50
            "New expiration period MUST be null or positive."
×
51
        }
52
    }
×
53

54
    fun replaceRejectedAlgorithmPreferencesAndFeatures(addNewAlgorithms: Boolean = false) = apply {
1✔
55
        val features = key.primaryKey.getFeatures(referenceTime)?.features ?: 0
1✔
56
        val newFeatures =
1✔
57
            Feature.fromBitmask(features.toInt())
1✔
58
                // Filter out unsupported features
59
                .filter { policy.featurePolicy.isAcceptable(it) }
1✔
60
                .toSet()
1✔
61
                // Optionally add in new capabilities
62
                .plus(
1✔
63
                    if (addNewAlgorithms) policy.keyGenerationAlgorithmSuite.features ?: listOf()
1✔
64
                    else listOf())
1✔
65
                .toTypedArray()
1✔
66
                .let { Feature.toBitmask(*it) }
1✔
67

68
        // Hash Algs
69
        val hashAlgs = key.primaryKey.hashAlgorithmPreferences.toHashAlgorithms()
1✔
70
        val newHashAlgs =
1✔
71
            hashAlgs
1✔
72
                // Filter out unsupported hash algorithms
73
                .filter { policy.dataSignatureHashAlgorithmPolicy.isAcceptable(it) }
1✔
74
                // Optionally add in new hash algorithms
75
                .plus(
1✔
76
                    if (addNewAlgorithms)
1✔
77
                        policy.keyGenerationAlgorithmSuite.hashAlgorithms ?: listOf()
1✔
78
                    else listOf())
1✔
79
                .toSet()
1✔
80

81
        // Sym Algs
82
        val symAlgs = key.primaryKey.symmetricCipherPreferences.toSymmetricKeyAlgorithms()
1✔
83
        val newSymAlgs =
1✔
84
            symAlgs
1✔
85
                .filter {
1✔
86
                    policy.messageEncryptionAlgorithmPolicy.symmetricAlgorithmPolicy.isAcceptable(
1✔
87
                        it)
1✔
88
                }
89
                .plus(
1✔
90
                    if (addNewAlgorithms)
1✔
91
                        policy.keyGenerationAlgorithmSuite.symmetricKeyAlgorithms ?: listOf()
1✔
92
                    else listOf())
1✔
93
                .toSet()
1✔
94

95
        // Comp Algs
96
        val compAlgs = key.primaryKey.compressionAlgorithmPreferences.toCompressionAlgorithms()
1✔
97
        val newCompAlgs =
1✔
98
            compAlgs
1✔
99
                .filter { policy.compressionAlgorithmPolicy.isAcceptable(it) }
1✔
100
                .plus(
1✔
101
                    if (addNewAlgorithms)
1✔
102
                        policy.keyGenerationAlgorithmSuite.compressionAlgorithms ?: listOf()
1✔
103
                    else listOf())
1✔
104
                .toSet()
1✔
105

106
        // AEAD Prefs
107
        val aeadAlgs = key.primaryKey.aeadCipherSuitePreferences.toAEADCipherModes()
1✔
108
        val newAeadAlgs =
1✔
109
            aeadAlgs
1✔
110
                .filter {
1✔
111
                    policy.messageEncryptionAlgorithmPolicy.isAcceptable(
1✔
112
                        MessageEncryptionMechanism.aead(
1✔
113
                            it.ciphermode.algorithmId, it.aeadAlgorithm.algorithmId))
1✔
114
                }
115
                .plus(policy.keyGenerationAlgorithmSuite.aeadAlgorithms ?: listOf())
1✔
116
                .toSet()
1✔
117

118
        if (features != newFeatures ||
1✔
119
            hashAlgs != newHashAlgs ||
1✔
120
            symAlgs != newSymAlgs ||
1✔
121
            compAlgs != newCompAlgs ||
1✔
122
            aeadAlgs != newAeadAlgs) {
1✔
123
            keyEditor.addDirectKeySignature(
×
124
                SignatureParameters.Callback.Util.modifyHashedSubpackets { sigGen ->
×
125
                    sigGen.apply {
×
126
                        setKeyFlags(key.primaryKey.keyFlags?.flags ?: 0)
×
127
                        setFeature(true, newFeatures)
×
128
                        setPreferredHashAlgorithms(
×
129
                            true, newHashAlgs.map { it.algorithmId }.toIntArray())
×
130
                        setPreferredSymmetricAlgorithms(
×
131
                            true, newSymAlgs.map { it.algorithmId }.toIntArray())
×
132
                        setPreferredCompressionAlgorithms(
×
133
                            true, newCompAlgs.map { it.algorithmId }.toIntArray())
×
134
                        setPreferredAEADCiphersuites(
×
135
                            true,
×
136
                            newAeadAlgs
×
137
                                .map {
×
138
                                    Combination(
×
139
                                        it.ciphermode.algorithmId, it.aeadAlgorithm.algorithmId)
×
140
                                }
141
                                .toTypedArray())
×
142
                    }
×
143
                })
144
        }
145
    }
1✔
146

147
    fun replaceWeakSubkeys(
×
148
        revokeWeakKeys: Boolean = true,
×
149
        signingKeysOnly: Boolean
150
    ): OpenPGPKeyUpdater = apply {
1✔
151
        replaceWeakSigningSubkeys(revokeWeakKeys)
1✔
152
        if (!signingKeysOnly) {
1✔
153
            replaceWeakEncryptionSubkeys(revokeWeakKeys)
1✔
154
        }
155
    }
1✔
156

157
    fun replaceWeakEncryptionSubkeys(
1✔
158
        revokeWeakKeys: Boolean,
159
        keyPairGeneratorCallback: KeyPairGeneratorCallback =
160
            KeyPairGeneratorCallback.Util.encryptionKey()
1✔
161
    ) {
162
        val encryptionKeys = key.getEncryptionKeys(referenceTime)
1✔
163
        val weakEncryptionKeys =
1✔
164
            key.getEncryptionKeys(referenceTime).filterNot {
1✔
165
                policy.publicKeyAlgorithmPolicy.isAcceptable(
1✔
166
                    it.algorithm, it.pgpPublicKey.bitStrength)
1✔
167
            }
168

169
        if (weakEncryptionKeys.isNotEmpty() || encryptionKeys.isEmpty()) {
1✔
170
            keyEditor.addEncryptionSubkey(keyPairGeneratorCallback)
1✔
171
        }
172

173
        if (revokeWeakKeys) {
1✔
174
            weakEncryptionKeys
×
175
                .filterNot { it.keyIdentifier.matches(key.primaryKey.keyIdentifier) }
×
176
                .forEach { keyEditor.revokeComponentKey(it) }
×
177
        }
178
    }
1✔
179

180
    fun replaceWeakSigningSubkeys(
1✔
181
        revokeWeakKeys: Boolean,
182
        keyPairGenerator: PGPKeyPairGenerator = provideKeyPairGenerator(),
1✔
183
        keyPairGeneratorCallback: KeyPairGeneratorCallback =
184
            KeyPairGeneratorCallback.Util.signingKey()
1✔
185
    ) {
186
        val weakSigningKeys =
1✔
187
            key.getSigningKeys(referenceTime).filterNot {
1✔
188
                policy.publicKeyAlgorithmPolicy.isAcceptable(
1✔
189
                    it.algorithm, it.pgpPublicKey.bitStrength)
1✔
190
            }
191

192
        if (weakSigningKeys.isNotEmpty()) {
1✔
193
            keyEditor.addSigningSubkey(keyPairGeneratorCallback)
×
194
        }
195

196
        if (revokeWeakKeys) {
1✔
197
            weakSigningKeys
×
198
                .filterNot { it.keyIdentifier.matches(key.primaryKey.keyIdentifier) }
×
199
                .forEach { keyEditor.revokeComponentKey(it) }
×
200
        }
201

202
        keyPairGeneratorCallback.generateFrom(keyPairGenerator)
1✔
203
    }
1✔
204

205
    private fun provideKeyPairGenerator(): PGPKeyPairGenerator {
206
        return api.implementation
1✔
207
            .pgpKeyPairGeneratorProvider()
1✔
208
            .get(key.primaryKey.getKeyVersion().numeric, referenceTime)
1✔
209
    }
210

211
    fun finish(): OpenPGPKey {
212
        return keyEditor.done()
1✔
213
    }
214

215
    companion object {
216
        const val SECOND: Long = 1000
217
        const val MINUTE: Long = 60 * SECOND
218
        const val HOUR: Long = 60 * MINUTE
219
        const val DAY: Long = 24 * HOUR
220
        const val YEAR: Long = 365 * DAY
221
        const val _5YEARS: Long = 5 * YEAR
222
    }
223
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc