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

pgpainless / cert-d-pgpainless / #3

29 Sep 2025 12:32PM UTC coverage: 42.053% (-4.8%) from 46.875%
#3

push

other

vanitasvitae
Bump PGPainless to 2.0.0, cert-d-java to 0.2.3

49 of 83 new or added lines in 7 files covered. (59.04%)

11 existing lines in 2 files now uncovered.

127 of 302 relevant lines covered (42.05%)

0.42 hits per line

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

69.41
/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/MergeCallbacks.java
1
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
2
//
3
// SPDX-License-Identifier: Apache-2.0
4

5
package org.pgpainless.certificate_store;
6

7
import org.bouncycastle.openpgp.PGPException;
8
import org.bouncycastle.openpgp.PGPPublicKey;
9
import org.bouncycastle.openpgp.PGPSecretKey;
10
import org.bouncycastle.openpgp.PGPSecretKeyRing;
11
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
12
import org.bouncycastle.openpgp.api.OpenPGPKey;
13
import org.pgpainless.PGPainless;
14
import org.pgpainless.key.OpenPgpFingerprint;
15
import pgp.certificate_store.certificate.KeyMaterial;
16
import pgp.certificate_store.certificate.KeyMaterialMerger;
17

18
import java.io.IOException;
19
import java.util.ArrayList;
20
import java.util.Arrays;
21
import java.util.Iterator;
22
import java.util.List;
23

24
public class MergeCallbacks {
×
25

26
    /**
27
     * Return a {@link KeyMaterialMerger} that merges the two copies of the same certificate (same primary key) into one
28
     * combined certificate.
29
     *
30
     * @return merging callback
31
     */
32
    public static KeyMaterialMerger mergeWithExisting() {
33
        return new KeyMaterialMerger() {
1✔
34

35
            @Override
36
            public KeyMaterial merge(KeyMaterial data, KeyMaterial existing)
37
                    throws IOException {
38
                // Simple cases: one is null -> return other
39
                if (data == null) {
1✔
40
                    return existing;
1✔
41
                }
42
                if (existing == null) {
1✔
43
                    return data;
1✔
44
                }
45

46
                PGPainless api = PGPainless.getInstance();
1✔
47

48
                OpenPGPCertificate existingCert = api.readKey().parseCertificateOrKey(existing.getInputStream());
1✔
49
                OpenPGPCertificate updatedCert = api.readKey().parseCertificateOrKey(data.getInputStream());
1✔
50

51
                OpenPGPCertificate mergedCert = mergeCertificates(updatedCert, existingCert);
1✔
52

53
                printOutDifferences(existingCert, mergedCert);
1✔
54
                return toKeyMaterial(mergedCert);
1✔
55
            }
56

57
            private OpenPGPCertificate mergeCertificates(OpenPGPCertificate updatedCertOrKey,
58
                                                         OpenPGPCertificate existingCertOrKey) {
59
                if (!existingCertOrKey.getKeyIdentifier().matchesExplicit(updatedCertOrKey.getKeyIdentifier())) {
1✔
NEW
60
                    throw new IllegalArgumentException("Not the same OpenPGP key/certificate: Mismatched primary key.");
×
61
                }
62

63
                OpenPGPCertificate merged;
64

65
                try {
66
                    if (existingCertOrKey.isSecretKey()) {
1✔
67
                        OpenPGPKey existingKey = (OpenPGPKey) existingCertOrKey;
1✔
68

69
                        if (updatedCertOrKey.isSecretKey()) {
1✔
70
                            // Merge key with key
NEW
71
                            OpenPGPKey updatedKey = (OpenPGPKey) updatedCertOrKey;
×
NEW
72
                            OpenPGPCertificate mergedCertPart = OpenPGPCertificate.join(
×
NEW
73
                                    existingKey.toCertificate(),
×
NEW
74
                                    updatedKey.toCertificate());
×
75

NEW
76
                            List<PGPSecretKey> mergedSecretKeys = new ArrayList<>();
×
NEW
77
                            Iterator<PGPSecretKey> existingKeysIterator = existingKey.getPGPSecretKeyRing().getSecretKeys();
×
NEW
78
                            while (existingKeysIterator.hasNext()) {
×
NEW
79
                                mergedSecretKeys.add(existingKeysIterator.next());
×
80
                            }
81

NEW
82
                            Iterator<PGPSecretKey> updatedKeysIterator = updatedKey.getPGPSecretKeyRing().getSecretKeys();
×
NEW
83
                            while (updatedKeysIterator.hasNext()) {
×
NEW
84
                                PGPSecretKey next = updatedKeysIterator.next();
×
NEW
85
                                if (existingKey.getPGPSecretKeyRing().getSecretKey(next.getKeyIdentifier()) == null) {
×
NEW
86
                                    mergedSecretKeys.add(next);
×
87
                                }
NEW
88
                            }
×
NEW
89
                            PGPSecretKeyRing mergedSecretKeyRing = new PGPSecretKeyRing(mergedSecretKeys);
×
NEW
90
                            merged = new OpenPGPKey(
×
NEW
91
                                    PGPSecretKeyRing.replacePublicKeys(
×
92
                                            mergedSecretKeyRing,
NEW
93
                                            mergedCertPart.getPGPPublicKeyRing()));
×
NEW
94
                        } else {
×
95
                            // Merge key with cert
96
                            OpenPGPCertificate mergedCertPart = OpenPGPCertificate.join(
1✔
97
                                    existingKey.toCertificate(),
1✔
98
                                    updatedCertOrKey);
99
                            merged = new OpenPGPKey(
1✔
100
                                    PGPSecretKeyRing.replacePublicKeys(
1✔
101
                                            existingKey.getPGPSecretKeyRing(),
1✔
102
                                            mergedCertPart.getPGPPublicKeyRing()));
1✔
103
                        }
104
                    } else {
1✔
105
                        if (updatedCertOrKey.isSecretKey()) {
1✔
106
                            // Swap update and existing cert
107
                            return mergeCertificates(existingCertOrKey, updatedCertOrKey);
1✔
108
                        }
109

110
                        // Merge cert with cert
111
                        return OpenPGPCertificate.join(existingCertOrKey, updatedCertOrKey);
1✔
112
                    }
113

114
                    return merged;
1✔
NEW
115
                } catch (PGPException e) {
×
NEW
116
                    throw new RuntimeException(e);
×
117
                }
118
            }
119

120
            private KeyMaterial toKeyMaterial(OpenPGPCertificate mergedCertificate)
121
                    throws IOException {
122
                if (mergedCertificate.isSecretKey()) {
1✔
123
                    return KeyFactory.keyFromOpenPGPKey((OpenPGPKey) mergedCertificate, null);
1✔
124
                } else {
125
                    return CertificateFactory.certificateFromOpenPGPCertificate(mergedCertificate, null);
1✔
126
                }
127
            }
128

129
            private void printOutDifferences(OpenPGPCertificate existingCert, OpenPGPCertificate mergedCert) throws IOException {
130
                int numSigsBefore = countSigs(existingCert);
1✔
131
                int numSigsAfter = countSigs(mergedCert);
1✔
132
                int newSigs = numSigsAfter - numSigsBefore;
1✔
133
                int numUidsBefore = count(existingCert.getAllUserIds().iterator());
1✔
134
                int numUidsAfter = count(mergedCert.getAllUserIds().iterator());
1✔
135
                int newUids = numUidsAfter - numUidsBefore;
1✔
136

137
                if (!Arrays.equals(existingCert.getEncoded(), mergedCert.getEncoded())) {
1✔
138
                    OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(mergedCert);
1✔
139
                    StringBuilder sb = new StringBuilder();
1✔
140
                    sb.append(String.format("Certificate %s has", fingerprint));
1✔
141
                    if (newSigs != 0) {
1✔
142
                        sb.append(String.format(" %d new signatures", newSigs));
1✔
143
                    }
144
                    if (newUids != 0) {
1✔
145
                        if (newSigs != 0) {
×
146
                            sb.append(" and");
×
147
                        }
148
                        sb.append(String.format(" %d new UIDs", newUids));
×
149
                    }
150
                    if (newSigs == 0 && newUids == 0) {
1✔
151
                        sb.append(" changed");
1✔
152
                    }
153

154
                    // In this case it is okay to print to stdout, since we are a CLI app
155
                    // CHECKSTYLE:OFF
156
                    System.out.println(sb);
1✔
157
                    // CHECKSTYLE:ON
158
                }
159
            }
1✔
160

161
            private int countSigs(OpenPGPCertificate keys) {
162
                int numSigs = 0;
1✔
163
                for (OpenPGPCertificate.OpenPGPComponentKey componentKey : keys.getKeys()) {
1✔
164
                    PGPPublicKey key = componentKey.getPGPPublicKey();
1✔
165
                    numSigs += count(key.getSignatures());
1✔
166
                }
1✔
167
                return numSigs;
1✔
168
            }
169

170
            // TODO: Use CollectionUtils.count() once available
171
            private int count(Iterator<?> iterator) {
172
                int num = 0;
1✔
173
                while (iterator.hasNext()) {
1✔
174
                    iterator.next();
1✔
175
                    num++;
1✔
176
                }
177
                return num;
1✔
178
            }
179
        };
180
    }
181

182
    public static KeyMaterialMerger overrideExisting() {
183
        // noinspection Convert2Lambda
184
        return new KeyMaterialMerger() {
1✔
185
            @Override
186
            public KeyMaterial merge(KeyMaterial data, KeyMaterial existing) {
187
                return data;
1✔
188
            }
189
        };
190
    }
191
}
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