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

pgpainless / pgpainless / #1039

pending completion
#1039

push

github-actions

vanitasvitae
Allow overriding evluation date in SigningOptions

7 of 7 new or added lines in 1 file covered. (100.0%)

7083 of 7953 relevant lines covered (89.06%)

0.89 hits per line

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

87.14
/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java
1
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
2
//
3
// SPDX-License-Identifier: Apache-2.0
4

5
package org.pgpainless.encryption_signing;
6

7
import java.util.Collections;
8
import java.util.Date;
9
import java.util.HashMap;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Set;
13
import javax.annotation.Nonnull;
14
import javax.annotation.Nullable;
15

16
import org.bouncycastle.openpgp.PGPException;
17
import org.bouncycastle.openpgp.PGPPrivateKey;
18
import org.bouncycastle.openpgp.PGPPublicKey;
19
import org.bouncycastle.openpgp.PGPSecretKey;
20
import org.bouncycastle.openpgp.PGPSecretKeyRing;
21
import org.bouncycastle.openpgp.PGPSignatureGenerator;
22
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
23
import org.pgpainless.PGPainless;
24
import org.pgpainless.algorithm.DocumentSignatureType;
25
import org.pgpainless.algorithm.HashAlgorithm;
26
import org.pgpainless.algorithm.PublicKeyAlgorithm;
27
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
28
import org.pgpainless.exception.KeyException;
29
import org.pgpainless.implementation.ImplementationFactory;
30
import org.pgpainless.key.OpenPgpFingerprint;
31
import org.pgpainless.key.SubkeyIdentifier;
32
import org.pgpainless.key.info.KeyRingInfo;
33
import org.pgpainless.key.protection.SecretKeyRingProtector;
34
import org.pgpainless.key.protection.UnlockSecretKey;
35
import org.pgpainless.policy.Policy;
36
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets;
37
import org.pgpainless.signature.subpackets.SignatureSubpackets;
38
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
39

40
public final class SigningOptions {
1✔
41

42
    /**
43
     * A method of signing.
44
     */
45
    public static final class SigningMethod {
46
        private final PGPSignatureGenerator signatureGenerator;
47
        private final boolean detached;
48
        private final HashAlgorithm hashAlgorithm;
49

50
        private SigningMethod(@Nonnull PGPSignatureGenerator signatureGenerator,
51
                              boolean detached,
52
                              @Nonnull HashAlgorithm hashAlgorithm) {
1✔
53
            this.signatureGenerator = signatureGenerator;
1✔
54
            this.detached = detached;
1✔
55
            this.hashAlgorithm = hashAlgorithm;
1✔
56
        }
1✔
57

58
        /**
59
         * Inline-signature method.
60
         * The resulting signature will be written into the message itself, together with a one-pass-signature packet.
61
         *
62
         * @param signatureGenerator signature generator
63
         * @param hashAlgorithm hash algorithm used to generate the signature
64
         * @return inline signing method
65
         */
66
        public static SigningMethod inlineSignature(@Nonnull PGPSignatureGenerator signatureGenerator,
67
                                                    @Nonnull HashAlgorithm hashAlgorithm) {
68
            return new SigningMethod(signatureGenerator, false, hashAlgorithm);
1✔
69
        }
70

71
        /**
72
         * Detached signing method.
73
         * The resulting signature will not be added to the message, and instead can be distributed separately
74
         * to the signed message.
75
         *
76
         * @param signatureGenerator signature generator
77
         * @param hashAlgorithm hash algorithm used to generate the signature
78
         * @return detached signing method
79
         */
80
        public static SigningMethod detachedSignature(@Nonnull PGPSignatureGenerator signatureGenerator,
81
                                                      @Nonnull HashAlgorithm hashAlgorithm) {
82
            return new SigningMethod(signatureGenerator, true, hashAlgorithm);
1✔
83
        }
84

85
        public boolean isDetached() {
86
            return detached;
1✔
87
        }
88

89
        public PGPSignatureGenerator getSignatureGenerator() {
90
            return signatureGenerator;
1✔
91
        }
92

93
        public HashAlgorithm getHashAlgorithm() {
94
            return hashAlgorithm;
1✔
95
        }
96
    }
97

98
    private final Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
1✔
99
    private HashAlgorithm hashAlgorithmOverride;
100
    private Date evaluationDate = new Date();
1✔
101

102
    @Nonnull
103
    public static SigningOptions get() {
104
        return new SigningOptions();
1✔
105
    }
106

107
    /**
108
     * Override the evaluation date for signing keys with the given date.
109
     *
110
     * @param evaluationDate new evaluation date
111
     * @return this
112
     */
113
    public SigningOptions setEvaluationDate(@Nonnull Date evaluationDate) {
114
        this.evaluationDate = evaluationDate;
×
115
        return this;
×
116
    }
117

118
    /**
119
     * Sign the message using an inline signature made by the provided signing key.
120
     *
121
     * @param signingKeyProtector protector to unlock the signing key
122
     * @param signingKey key ring containing the signing key
123
     * @return this
124
     *
125
     * @throws KeyException if something is wrong with the key
126
     * @throws PGPException if the key cannot be unlocked or a signing method cannot be created
127
     */
128
    @Nonnull
129
    public SigningOptions addSignature(@Nonnull SecretKeyRingProtector signingKeyProtector,
130
                                       @Nonnull PGPSecretKeyRing signingKey)
131
            throws PGPException {
132
        return addInlineSignature(signingKeyProtector, signingKey, DocumentSignatureType.BINARY_DOCUMENT);
1✔
133
    }
134

135
    /**
136
     * Add inline signatures with all secret key rings in the provided secret key ring collection.
137
     *
138
     * @param secrectKeyDecryptor decryptor to unlock the signing secret keys
139
     * @param signingKeys collection of signing keys
140
     * @param signatureType type of signature (binary, canonical text)
141
     * @return this
142
     *
143
     * @throws KeyException if something is wrong with any of the keys
144
     * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
145
     */
146
    @Nonnull
147
    public SigningOptions addInlineSignatures(@Nonnull SecretKeyRingProtector secrectKeyDecryptor,
148
                                              @Nonnull Iterable<PGPSecretKeyRing> signingKeys,
149
                                              @Nonnull DocumentSignatureType signatureType)
150
            throws KeyException, PGPException {
151
        for (PGPSecretKeyRing signingKey : signingKeys) {
×
152
            addInlineSignature(secrectKeyDecryptor, signingKey, signatureType);
×
153
        }
×
154
        return this;
×
155
    }
156

157
    /**
158
     * Add an inline-signature.
159
     * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
160
     * of one-pass-signature packets.
161
     *
162
     * @param secretKeyDecryptor decryptor to unlock the signing secret key
163
     * @param secretKey signing key
164
     * @param signatureType type of signature (binary, canonical text)
165
     * @return this
166
     *
167
     * @throws KeyException if something is wrong with the key
168
     * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
169
     */
170
    @Nonnull
171
    public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
172
                                             @Nonnull PGPSecretKeyRing secretKey,
173
                                             @Nonnull DocumentSignatureType signatureType)
174
            throws KeyException, PGPException {
175
        return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
1✔
176
    }
177

178
    /**
179
     * Add an inline-signature.
180
     * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
181
     * of one-pass-signature packets.
182
     * <p>
183
     * This method uses the passed in user-id to select user-specific hash algorithms.
184
     *
185
     * @param secretKeyDecryptor decryptor to unlock the signing secret key
186
     * @param secretKey signing key
187
     * @param userId user-id of the signer
188
     * @param signatureType signature type (binary, canonical text)
189
     * @return this
190
     *
191
     * @throws KeyException if something is wrong with the key
192
     * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
193
     */
194
    @Nonnull
195
    public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
196
                                             @Nonnull PGPSecretKeyRing secretKey,
197
                                             @Nullable CharSequence userId,
198
                                             @Nonnull DocumentSignatureType signatureType)
199
            throws KeyException, PGPException {
200
        return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
1✔
201
    }
202

203
    /**
204
     * Add an inline-signature.
205
     * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
206
     * of one-pass-signature packets.
207
     * <p>
208
     * This method uses the passed in user-id to select user-specific hash algorithms.
209
     *
210
     * @param secretKeyDecryptor decryptor to unlock the signing secret key
211
     * @param secretKey signing key
212
     * @param userId user-id of the signer
213
     * @param signatureType signature type (binary, canonical text)
214
     * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
215
     * @return this
216
     *
217
     * @throws KeyException if the key is invalid
218
     * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
219
     */
220
    @Nonnull
221
    public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
222
                                             @Nonnull PGPSecretKeyRing secretKey,
223
                                             @Nullable CharSequence userId,
224
                                             @Nonnull DocumentSignatureType signatureType,
225
                                             @Nullable BaseSignatureSubpackets.Callback subpacketsCallback)
226
            throws KeyException, PGPException {
227
        KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
1✔
228
        if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
1✔
229
            throw new KeyException.UnboundUserIdException(
1✔
230
                    OpenPgpFingerprint.of(secretKey),
1✔
231
                    userId.toString(),
1✔
232
                    keyRingInfo.getLatestUserIdCertification(userId),
1✔
233
                    keyRingInfo.getUserIdRevocation(userId)
1✔
234
            );
235
        }
236

237
        List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
1✔
238
        if (signingPubKeys.isEmpty()) {
1✔
239
            throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
1✔
240
        }
241

242
        for (PGPPublicKey signingPubKey : signingPubKeys) {
1✔
243
            PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
1✔
244
            if (signingSecKey == null) {
1✔
245
                throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
1✔
246
            }
247
            PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
1✔
248
            Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
1✔
249
                    : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
1✔
250
            HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
1✔
251
            addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
1✔
252
        }
1✔
253

254
        return this;
1✔
255
    }
256

257
    /**
258
     * Create a binary inline signature using the signing key with the given keyId.
259
     *
260
     * @param secretKeyDecryptor decryptor to unlock the secret key
261
     * @param secretKey secret key ring
262
     * @param keyId keyId of the signing (sub-)key
263
     * @return builder
264
     * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
265
     * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
266
     * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
267
     */
268
    @Nonnull
269
    public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
270
                                             @Nonnull PGPSecretKeyRing secretKey,
271
                                             long keyId) throws PGPException {
272
        return addInlineSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null);
1✔
273
    }
274

275

276
    /**
277
     * Create an inline signature using the signing key with the given keyId.
278
     *
279
     * @param secretKeyDecryptor decryptor to unlock the secret key
280
     * @param secretKey secret key ring
281
     * @param keyId keyId of the signing (sub-)key
282
     * @param signatureType signature type
283
     * @param subpacketsCallback callback to modify the signatures subpackets
284
     * @return builder
285
     * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
286
     * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
287
     * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
288
     */
289
    @Nonnull
290
    public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
291
                                             @Nonnull PGPSecretKeyRing secretKey,
292
                                             long keyId,
293
                                             @Nonnull DocumentSignatureType signatureType,
294
                                             @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException {
295
        KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
1✔
296

297
        List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
1✔
298
        if (signingPubKeys.isEmpty()) {
1✔
299
            throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
×
300
        }
301

302
        for (PGPPublicKey signingPubKey : signingPubKeys) {
1✔
303
            if (signingPubKey.getKeyID() == keyId) {
1✔
304

305
                PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
1✔
306
                if (signingSecKey == null) {
1✔
307
                    throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
×
308
                }
309
                PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
1✔
310
                Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
1✔
311
                HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
1✔
312
                addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
1✔
313
                return this;
1✔
314
            }
315
        }
1✔
316

317
        throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId);
×
318
    }
319

320
    /**
321
     * Add detached signatures with all key rings from the provided secret key ring collection.
322
     *
323
     * @param secretKeyDecryptor decryptor to unlock the secret signing keys
324
     * @param signingKeys collection of signing key rings
325
     * @param signatureType type of the signature (binary, canonical text)
326
     * @return this
327
     *
328
     * @throws KeyException if something is wrong with any of the keys
329
     * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
330
     */
331
    @Nonnull
332
    public SigningOptions addDetachedSignatures(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
333
                                                @Nonnull Iterable<PGPSecretKeyRing> signingKeys,
334
                                                @Nonnull DocumentSignatureType signatureType)
335
            throws PGPException {
336
        for (PGPSecretKeyRing signingKey : signingKeys) {
×
337
            addDetachedSignature(secretKeyDecryptor, signingKey, signatureType);
×
338
        }
×
339
        return this;
×
340
    }
341

342
    /**
343
     * Create a detached signature.
344
     * The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}.
345
     *
346
     * @param secretKeyDecryptor decryptor to unlock the secret signing key
347
     * @param signingKey signing key
348
     * @return this
349
     *
350
     * @throws KeyException if something is wrong with the key
351
     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
352
     */
353
    @Nonnull
354
    public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
355
                                               @Nonnull PGPSecretKeyRing signingKey)
356
            throws PGPException {
357
        return addDetachedSignature(secretKeyDecryptor, signingKey, DocumentSignatureType.BINARY_DOCUMENT);
1✔
358
    }
359

360
    /**
361
     * Create a detached signature.
362
     * Detached signatures are not being added into the PGP message itself.
363
     * Instead, they can be distributed separately to the message.
364
     * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
365
     *
366
     * @param secretKeyDecryptor decryptor to unlock the secret signing key
367
     * @param secretKey signing key
368
     * @param signatureType type of data that is signed (binary, canonical text)
369
     * @return this
370
     *
371
     * @throws KeyException if something is wrong with the key
372
     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
373
     */
374
    @Nonnull
375
    public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
376
                                               @Nonnull PGPSecretKeyRing secretKey,
377
                                               @Nonnull DocumentSignatureType signatureType)
378
            throws PGPException {
379
        return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType);
1✔
380
    }
381

382
    /**
383
     * Create a detached signature.
384
     * Detached signatures are not being added into the PGP message itself.
385
     * Instead, they can be distributed separately to the message.
386
     * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
387
     * <p>
388
     * This method uses the passed in user-id to select user-specific hash algorithms.
389
     *
390
     * @param secretKeyDecryptor decryptor to unlock the secret signing key
391
     * @param secretKey signing key
392
     * @param userId user-id
393
     * @param signatureType type of data that is signed (binary, canonical text)
394
     * @return this
395
     *
396
     * @throws KeyException if something is wrong with the key
397
     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
398
     */
399
    @Nonnull
400
    public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
401
                                               @Nonnull PGPSecretKeyRing secretKey,
402
                                               @Nullable CharSequence userId,
403
                                               @Nonnull DocumentSignatureType signatureType)
404
            throws PGPException {
405
        return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
1✔
406
    }
407

408
    /**
409
     * Create a detached signature.
410
     * Detached signatures are not being added into the PGP message itself.
411
     * Instead, they can be distributed separately to the message.
412
     * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
413
     * <p>
414
     * This method uses the passed in user-id to select user-specific hash algorithms.
415
     *
416
     * @param secretKeyDecryptor decryptor to unlock the secret signing key
417
     * @param secretKey signing key
418
     * @param userId user-id
419
     * @param signatureType type of data that is signed (binary, canonical text)
420
     * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
421
     * @return this
422
     *
423
     * @throws KeyException if something is wrong with the key
424
     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
425
     */
426
    @Nonnull
427
    public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
428
                                               @Nonnull PGPSecretKeyRing secretKey,
429
                                               @Nullable CharSequence userId,
430
                                               @Nonnull DocumentSignatureType signatureType,
431
                                               @Nullable BaseSignatureSubpackets.Callback subpacketCallback)
432
            throws PGPException {
433
        KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
1✔
434
        if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
1✔
435
            throw new KeyException.UnboundUserIdException(
1✔
436
                    OpenPgpFingerprint.of(secretKey),
1✔
437
                    userId.toString(),
1✔
438
                    keyRingInfo.getLatestUserIdCertification(userId),
1✔
439
                    keyRingInfo.getUserIdRevocation(userId)
1✔
440
            );
441
        }
442

443
        List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
1✔
444
        if (signingPubKeys.isEmpty()) {
1✔
445
            throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
1✔
446
        }
447

448
        for (PGPPublicKey signingPubKey : signingPubKeys) {
1✔
449
            PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
1✔
450
            if (signingSecKey == null) {
1✔
451
                throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
1✔
452
            }
453
            PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
1✔
454
            Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
1✔
455
                    : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
1✔
456
            HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
1✔
457
            addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true);
1✔
458
        }
1✔
459

460
        return this;
1✔
461
    }
462

463
    /**
464
     * Create a detached binary signature using the signing key with the given keyId.
465
     *
466
     * @param secretKeyDecryptor decryptor to unlock the secret key
467
     * @param secretKey secret key ring
468
     * @param keyId keyId of the signing (sub-)key
469
     * @return builder
470
     * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
471
     * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
472
     * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
473
     */
474
    @Nonnull
475
    public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
476
                                             @Nonnull PGPSecretKeyRing secretKey,
477
                                             long keyId) throws PGPException {
478
        return addDetachedSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null);
1✔
479
    }
480

481
    /**
482
     * Create a detached signature using the signing key with the given keyId.
483
     *
484
     * @param secretKeyDecryptor decryptor to unlock the secret key
485
     * @param secretKey secret key ring
486
     * @param keyId keyId of the signing (sub-)key
487
     * @param signatureType signature type
488
     * @param subpacketsCallback callback to modify the signatures subpackets
489
     * @return builder
490
     * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
491
     * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
492
     * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
493
     */
494
    @Nonnull
495
    public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor,
496
                                             @Nonnull PGPSecretKeyRing secretKey,
497
                                             long keyId,
498
                                             @Nonnull DocumentSignatureType signatureType,
499
                                             @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException {
500
        KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate);
1✔
501

502
        List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
1✔
503
        if (signingPubKeys.isEmpty()) {
1✔
504
            throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey));
×
505
        }
506

507
        for (PGPPublicKey signingPubKey : signingPubKeys) {
1✔
508
            if (signingPubKey.getKeyID() == keyId) {
1✔
509

510
                PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
1✔
511
                if (signingSecKey == null) {
1✔
512
                    throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID());
×
513
                }
514
                PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
1✔
515
                Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
1✔
516
                HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
1✔
517
                addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, true);
1✔
518
                return this;
1✔
519
            }
520
        }
1✔
521

522
        throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId);
×
523
    }
524

525
    private void addSigningMethod(@Nonnull PGPSecretKeyRing secretKey,
526
                                  @Nonnull PGPPrivateKey signingSubkey,
527
                                  @Nullable BaseSignatureSubpackets.Callback subpacketCallback,
528
                                  @Nonnull HashAlgorithm hashAlgorithm,
529
                                  @Nonnull DocumentSignatureType signatureType,
530
                                  boolean detached)
531
            throws PGPException {
532
        SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID());
1✔
533
        PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID());
1✔
534
        PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(signingSecretKey.getPublicKey().getAlgorithm());
1✔
535
        int bitStrength = signingSecretKey.getPublicKey().getBitStrength();
1✔
536
        if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
1✔
537
            throw new KeyException.UnacceptableSigningKeyException(
1✔
538
                    new KeyException.PublicKeyAlgorithmPolicyException(
539
                            OpenPgpFingerprint.of(secretKey), signingSecretKey.getKeyID(), publicKeyAlgorithm, bitStrength));
1✔
540
        }
541

542
        PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType);
1✔
543

544
        // Subpackets
545
        SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey());
1✔
546
        SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets();
1✔
547
        if (subpacketCallback != null) {
1✔
548
            subpacketCallback.modifyHashedSubpackets(hashedSubpackets);
×
549
            subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets);
×
550
        }
551
        generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets));
1✔
552
        generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets));
1✔
553

554
        SigningMethod signingMethod = detached ?
1✔
555
                SigningMethod.detachedSignature(generator, hashAlgorithm) :
1✔
556
                SigningMethod.inlineSignature(generator, hashAlgorithm);
1✔
557
        signingMethods.put(signingKeyIdentifier, signingMethod);
1✔
558
    }
1✔
559

560
    /**
561
     * Negotiate, which hash algorithm to use.
562
     * <p>
563
     * This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}.
564
     * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
565
     * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is
566
     * used as a fallback.
567
     *
568
     * @param preferences preferences
569
     * @param policy policy
570
     * @return selected hash algorithm
571
     */
572
    @Nonnull
573
    private HashAlgorithm negotiateHashAlgorithm(@Nonnull Set<HashAlgorithm> preferences,
574
                                                 @Nonnull Policy policy) {
575
        if (hashAlgorithmOverride != null) {
1✔
576
            return hashAlgorithmOverride;
1✔
577
        }
578

579
        return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy)
1✔
580
                .negotiateHashAlgorithm(preferences);
1✔
581
    }
582

583
    @Nonnull
584
    private PGPSignatureGenerator createSignatureGenerator(@Nonnull PGPPrivateKey privateKey,
585
                                                           @Nonnull HashAlgorithm hashAlgorithm,
586
                                                           @Nonnull DocumentSignatureType signatureType)
587
            throws PGPException {
588
        int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm();
1✔
589
        PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance()
1✔
590
                .getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId());
1✔
591
        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
1✔
592
        signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey);
1✔
593

594
        return signatureGenerator;
1✔
595
    }
596

597
    /**
598
     * Return a map of key-ids and signing methods.
599
     * For internal use.
600
     *
601
     * @return signing methods
602
     */
603
    @Nonnull
604
    Map<SubkeyIdentifier, SigningMethod> getSigningMethods() {
605
        return Collections.unmodifiableMap(signingMethods);
1✔
606
    }
607

608
    /**
609
     * Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
610
     * If no override has been set, an accetable algorithm will be negotiated instead.
611
     * <p>
612
     * Note: To override the hash algorithm for signing, call this method *before* calling
613
     * {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or
614
     * {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}.
615
     *
616
     * @param hashAlgorithmOverride override hash algorithm
617
     * @return this
618
     */
619
    @Nonnull
620
    public SigningOptions overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithmOverride) {
621
        this.hashAlgorithmOverride = hashAlgorithmOverride;
1✔
622
        return this;
1✔
623
    }
624

625
    /**
626
     * Return the hash algorithm override (or null if no override is set).
627
     *
628
     * @return hash algorithm override
629
     */
630
    @Nullable
631
    public HashAlgorithm getHashAlgorithmOverride() {
632
        return hashAlgorithmOverride;
1✔
633
    }
634
}
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