SigningKeyGenerator.java

1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright 2026 Egothor
3
// Copyright 2026 Accenture
4
package org.egothor.methodatlas.evidence;
5
6
import java.io.BufferedWriter;
7
import java.io.IOException;
8
import java.lang.reflect.Constructor;
9
import java.lang.reflect.InvocationTargetException;
10
import java.lang.reflect.Method;
11
import java.nio.charset.StandardCharsets;
12
import java.nio.file.Files;
13
import java.nio.file.Path;
14
import java.nio.file.attribute.PosixFilePermissions;
15
import java.security.GeneralSecurityException;
16
import java.security.KeyPair;
17
import java.security.PublicKey;
18
import java.util.Locale;
19
import java.util.Map;
20
import java.util.logging.Level;
21
import java.util.logging.Logger;
22
23
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
24
25
import zeroecho.core.CryptoAlgorithm;
26
import zeroecho.core.CryptoAlgorithms;
27
import zeroecho.core.spec.AlgorithmKeySpec;
28
import zeroecho.core.storage.KeyringStore;
29
import zeroecho.sdk.util.BouncyCastleActivator;
30
31
/**
32
 * Generates an asymmetric signing key pair and stores it in a ZeroEcho
33
 * {@link KeyringStore} so that {@code -evidence-pack} can later sign manifests
34
 * with it.
35
 *
36
 * <p>
37
 * The keyring is a plaintext UTF-8 file (see {@link KeyringStore}); it is
38
 * <em>not</em> a JDK PKCS12/JKS keystore. Because the file stores the private
39
 * key in clear text, the generator restricts the file to owner read/write
40
 * (POSIX {@code 0600}) after writing it; on platforms without POSIX
41
 * permissions a warning is emitted instructing the operator to restrict access
42
 * manually.
43
 * </p>
44
 *
45
 * <p>
46
 * Only asymmetric signing algorithms are offered, and the offered set is
47
 * deliberately limited to the algorithms covered by round-trip tests: the
48
 * default {@value #DEFAULT_ALGORITHM}, plus {@code RSA}, {@code ECDSA}, and
49
 * {@code SPHINCS+}. Storing a key requires an algorithm-specific
50
 * {@code AlgorithmKeySpec}; the spec class is discovered from the algorithm's
51
 * {@link CryptoAlgorithm#asymmetricBuildersInfo()} and instantiated from the
52
 * encoded key material by reflection — the same mechanism ZeroEcho's own
53
 * {@code KeyStoreManagement} tool uses.
54
 * </p>
55
 *
56
 * <p>
57
 * This type is a non-instantiable utility holder and is not thread-safe with
58
 * respect to concurrent writers of the same keyring file.
59
 * </p>
60
 *
61
 * @see ZeroEchoSigner
62
 * @see KeyringStore
63
 * @since 4.0.0
64
 */
65
final class SigningKeyGenerator {
66
67
    private static final Logger LOG = Logger.getLogger(SigningKeyGenerator.class.getName());
68
69
    /** Default signing algorithm when the caller supplies none. */
70
    /* default */ static final String DEFAULT_ALGORITHM = "Ed25519";
71
72
    /** Owner read/write only — the keyring holds a clear-text private key. */
73
    private static final String OWNER_ONLY_PERMISSIONS = "rw-------";
74
75
    /** Filename suffix for the exported X.509 public key in PEM form. */
76
    private static final String PUBLIC_PEM_SUFFIX = "-public.pem";
77
78
    /**
79
     * Canonical algorithm tokens accepted by the generator, keyed by their
80
     * upper-case form so lookups are case-insensitive while the stored id keeps
81
     * ZeroEcho's exact spelling.
82
     */
83
    private static final Map<String, String> SUPPORTED_ALGORITHMS = Map.of(
84
            "ED25519", "Ed25519",
85
            "RSA", "RSA",
86
            "ECDSA", "ECDSA",
87
            "SPHINCS+", "SPHINCS+");
88
89
    /** Prevents instantiation. */
90
    private SigningKeyGenerator() {
91
    }
92
93
    /**
94
     * Outcome of a successful key generation.
95
     *
96
     * @param algorithm     canonical algorithm id that was generated
97
     * @param publicAlias   alias under which the public key was stored
98
     * @param privateAlias  alias under which the private key was stored
99
     * @param keyringFile   keyring file the key pair was written to
100
     * @param publicKeyPem  companion file holding the public key in X.509 PEM
101
     *                      form, for verification with standard tools
102
     * @since 4.0.0
103
     */
104
    /* default */ record GeneratedKey(String algorithm, String publicAlias, String privateAlias, Path keyringFile,
105
            Path publicKeyPem) {
106
    }
107
108
    /**
109
     * Returns the canonical algorithm ids this generator supports, in a stable,
110
     * human-readable order suitable for help text.
111
     *
112
     * @return comma-separated list of supported algorithm ids; never empty
113
     */
114
    /* default */ static String supportedAlgorithmsHint() {
115 1 1. supportedAlgorithmsHint : replaced return value with "" for org/egothor/methodatlas/evidence/SigningKeyGenerator::supportedAlgorithmsHint → SURVIVED
        return "Ed25519 (default), RSA, ECDSA, SPHINCS+";
116
    }
117
118
    /**
119
     * Generates a signing key pair and stores it under {@code alias} in the
120
     * keyring at {@code keyringFile}.
121
     *
122
     * <p>
123
     * When the keyring file already exists it is loaded and the new entries are
124
     * appended; otherwise a fresh keyring is created. The private key is written
125
     * in clear text, so a fresh keyring is created with owner-only permissions
126
     * (POSIX {@code 0600}) <em>before</em> it is written and re-verified afterwards
127
     * (see {@link #createOwnerOnlyFile(Path)} and {@link #restrictPermissions(Path)}),
128
     * leaving no window in which a newly created keyring is world-readable. The
129
     * public key is
130
     * additionally exported next to the keyring as an X.509 PEM file
131
     * ({@code <alias>-public.pem}) so a manifest signature can be verified with
132
     * standard tooling such as {@code openssl}.
133
     * </p>
134
     *
135
     * @param keyringFile target keyring file; must not be {@code null}
136
     * @param alias       base alias for the key pair; ZeroEcho stores it as
137
     *                    {@code alias.pub} and {@code alias.priv}; must not be
138
     *                    {@code null} or blank
139
     * @param algorithm   algorithm id ({@code null} selects
140
     *                    {@value #DEFAULT_ALGORITHM}); must be one of the
141
     *                    supported ids, case-insensitively
142
     * @param overwrite   when {@code true}, an existing entry under {@code alias}
143
     *                    is replaced; when {@code false}, a collision is an error
144
     * @return a {@link GeneratedKey} describing what was written
145
     * @throws IOException              if the keyring cannot be read or written
146
     * @throws GeneralSecurityException if key generation or spec construction fails
147
     * @throws IllegalArgumentException if {@code alias} is blank or {@code algorithm}
148
     *                                  is not supported
149
     */
150
    /* default */ static GeneratedKey generate(Path keyringFile, String alias, String algorithm, boolean overwrite)
151
            throws IOException, GeneralSecurityException {
152 2 1. generate : removed conditional - replaced equality check with false → TIMED_OUT
2. generate : removed conditional - replaced equality check with true → KILLED
        if (keyringFile == null) {
153
            throw new IllegalArgumentException("keyringFile must not be null");
154
        }
155 4 1. generate : removed conditional - replaced equality check with true → SURVIVED
2. generate : removed conditional - replaced equality check with false → SURVIVED
3. generate : removed conditional - replaced equality check with false → KILLED
4. generate : removed conditional - replaced equality check with true → KILLED
        if (alias == null || alias.isBlank()) {
156
            throw new IllegalArgumentException("alias must not be blank");
157
        }
158
        String canonical = canonicalAlgorithm(algorithm);
159
160 1 1. generate : removed call to zeroecho/sdk/util/BouncyCastleActivator::init → TIMED_OUT
        BouncyCastleActivator.init();
161 2 1. generate : removed conditional - replaced equality check with false → KILLED
2. generate : removed conditional - replaced equality check with true → KILLED
        KeyringStore store = Files.exists(keyringFile) ? KeyringStore.load(keyringFile) : new KeyringStore();
162 4 1. generate : removed conditional - replaced equality check with false → KILLED
2. generate : removed conditional - replaced equality check with true → KILLED
3. generate : removed conditional - replaced equality check with true → KILLED
4. generate : removed conditional - replaced equality check with false → KILLED
        if (!overwrite && store.contains(alias)) {
163
            throw new IOException("Alias already exists in keyring (use -overwrite to replace): " + alias);
164
        }
165
166
        CryptoAlgorithm alg = requireAsymmetric(canonical);
167
        KeyPair keyPair = alg.generateKeyPair();
168
169
        Class<? extends AlgorithmKeySpec> publicSpecType = findSpecType(alg, "Public");
170
        Class<? extends AlgorithmKeySpec> privateSpecType = findSpecType(alg, "Private");
171
172
        AlgorithmKeySpec publicSpec = importSpec(publicSpecType, keyPair.getPublic().getEncoded(), canonical);
173
        AlgorithmKeySpec privateSpec = importSpec(privateSpecType, keyPair.getPrivate().getEncoded(), canonical);
174
175 1 1. generate : removed call to zeroecho/core/storage/KeyringStore::putPublic → KILLED
        store.putPublic(alias, canonical, publicSpec);
176 1 1. generate : removed call to zeroecho/core/storage/KeyringStore::putPrivate → KILLED
        store.putPrivate(alias, canonical, privateSpec);
177 1 1. generate : removed call to org/egothor/methodatlas/evidence/SigningKeyGenerator::createOwnerOnlyFile → SURVIVED
        createOwnerOnlyFile(keyringFile);
178 1 1. generate : removed call to zeroecho/core/storage/KeyringStore::save → KILLED
        store.save(keyringFile);
179 1 1. generate : removed call to org/egothor/methodatlas/evidence/SigningKeyGenerator::restrictPermissions → SURVIVED
        restrictPermissions(keyringFile);
180
181
        Path publicKeyPem = keyringFile.resolveSibling(alias + PUBLIC_PEM_SUFFIX);
182 1 1. generate : removed call to org/egothor/methodatlas/evidence/SigningKeyGenerator::writePublicKeyPem → KILLED
        writePublicKeyPem(keyPair.getPublic(), publicKeyPem);
183
184 1 1. generate : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::generate → KILLED
        return new GeneratedKey(canonical, alias + ".pub", alias + ".priv", keyringFile, publicKeyPem);
185
    }
186
187
    /**
188
     * Writes {@code publicKey} to {@code pemFile} as an X.509
189
     * {@code SubjectPublicKeyInfo} in PEM form ({@code -----BEGIN PUBLIC KEY-----}).
190
     *
191
     * <p>
192
     * The PEM is produced with Bouncy Castle's {@link JcaPEMWriter} — the same
193
     * mechanism ZeroEcho uses — so it is readable by standard tooling such as
194
     * {@code openssl}. The public key is not secret, so the file is left with
195
     * default permissions; it lets auditors verify a signed manifest without
196
     * trusting MethodAtlas or ZeroEcho.
197
     * </p>
198
     *
199
     * @param publicKey public key to export; must not be {@code null}
200
     * @param pemFile   destination PEM file
201
     * @throws IOException if the PEM cannot be written
202
     */
203
    private static void writePublicKeyPem(PublicKey publicKey, Path pemFile) throws IOException {
204
        try (BufferedWriter writer = Files.newBufferedWriter(pemFile, StandardCharsets.UTF_8);
205
                JcaPEMWriter pemWriter = new JcaPEMWriter(writer)) {
206 1 1. writePublicKeyPem : removed call to org/bouncycastle/openssl/jcajce/JcaPEMWriter::writeObject → KILLED
            pemWriter.writeObject(publicKey);
207
        }
208
    }
209
210
    /**
211
     * Resolves the requested algorithm to its canonical ZeroEcho id.
212
     *
213
     * @param algorithm requested id, or {@code null} for the default
214
     * @return canonical id
215
     * @throws IllegalArgumentException if the algorithm is not supported
216
     */
217
    private static String canonicalAlgorithm(String algorithm) {
218 2 1. canonicalAlgorithm : removed conditional - replaced equality check with false → KILLED
2. canonicalAlgorithm : removed conditional - replaced equality check with true → KILLED
        String requested = algorithm == null ? DEFAULT_ALGORITHM : algorithm;
219
        String canonical = SUPPORTED_ALGORITHMS.get(requested.toUpperCase(Locale.ROOT));
220 2 1. canonicalAlgorithm : removed conditional - replaced equality check with false → KILLED
2. canonicalAlgorithm : removed conditional - replaced equality check with true → KILLED
        if (canonical == null) {
221
            throw new IllegalArgumentException("Unsupported signing algorithm '" + requested
222
                    + "'. Supported: " + supportedAlgorithmsHint());
223
        }
224 1 1. canonicalAlgorithm : replaced return value with "" for org/egothor/methodatlas/evidence/SigningKeyGenerator::canonicalAlgorithm → KILLED
        return canonical;
225
    }
226
227
    /**
228
     * Resolves the algorithm from the ZeroEcho catalog and verifies it exposes an
229
     * asymmetric builder.
230
     *
231
     * @param canonical canonical algorithm id
232
     * @return the catalog algorithm
233
     * @throws GeneralSecurityException if the algorithm is absent from the catalog
234
     *                                  or has no asymmetric builder
235
     */
236
    private static CryptoAlgorithm requireAsymmetric(String canonical) throws GeneralSecurityException {
237
        final CryptoAlgorithm alg;
238
        try {
239
            alg = CryptoAlgorithms.require(canonical);
240
        } catch (IllegalArgumentException e) {
241
            throw new GeneralSecurityException("Algorithm '" + canonical
242
                    + "' is not available in the ZeroEcho catalog", e);
243
        }
244 2 1. requireAsymmetric : removed conditional - replaced equality check with false → TIMED_OUT
2. requireAsymmetric : removed conditional - replaced equality check with true → KILLED
        if (alg.asymmetricBuildersInfo().isEmpty()) {
245
            throw new GeneralSecurityException("Algorithm '" + canonical + "' has no asymmetric key builder");
246
        }
247 1 1. requireAsymmetric : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::requireAsymmetric → KILLED
        return alg;
248
    }
249
250
    /**
251
     * Finds the import-spec class for a public or private key by matching the
252
     * spec class simple name against {@code marker} (for example {@code "Public"}
253
     * or {@code "Private"}).
254
     *
255
     * @param alg    catalog algorithm to inspect
256
     * @param marker substring that identifies the key role in the spec class name
257
     * @return the matching spec class
258
     * @throws GeneralSecurityException if no matching spec class is registered
259
     */
260
    private static Class<? extends AlgorithmKeySpec> findSpecType(CryptoAlgorithm alg, String marker)
261
            throws GeneralSecurityException {
262
        for (CryptoAlgorithm.AsymBuilderInfo info : alg.asymmetricBuildersInfo()) {
263 2 1. findSpecType : removed conditional - replaced equality check with true → KILLED
2. findSpecType : removed conditional - replaced equality check with false → KILLED
            if (info.specType.getSimpleName().contains(marker)) {
264 1 1. findSpecType : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::findSpecType → KILLED
                return info.specType;
265
            }
266
        }
267
        throw new GeneralSecurityException("Algorithm '" + alg.id() + "' exposes no "
268
                + marker.toLowerCase(Locale.ROOT) + "-key import spec");
269
    }
270
271
    /**
272
     * Constructs an {@link AlgorithmKeySpec} from encoded key material using the
273
     * conventional ZeroEcho factory methods, in order: {@code fromRaw(byte[])},
274
     * {@code of(byte[])}, then a single {@code byte[]} constructor.
275
     *
276
     * @param specType target spec class
277
     * @param material encoded key bytes (X.509 SPKI for public keys, PKCS#8 for
278
     *                 private keys)
279
     * @param algId    algorithm id, used only for diagnostics
280
     * @return the constructed spec
281
     * @throws GeneralSecurityException if none of the known factories accept the
282
     *                                  material
283
     */
284
    private static AlgorithmKeySpec importSpec(Class<? extends AlgorithmKeySpec> specType, byte[] material,
285
            String algId) throws GeneralSecurityException {
286
        AlgorithmKeySpec viaFactory = invokeStaticFactory(specType, "fromRaw", material);
287 2 1. importSpec : removed conditional - replaced equality check with true → SURVIVED
2. importSpec : removed conditional - replaced equality check with false → TIMED_OUT
        if (viaFactory == null) {
288
            viaFactory = invokeStaticFactory(specType, "of", material);
289
        }
290 2 1. importSpec : removed conditional - replaced equality check with false → TIMED_OUT
2. importSpec : removed conditional - replaced equality check with true → KILLED
        if (viaFactory != null) {
291 1 1. importSpec : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::importSpec → NO_COVERAGE
            return viaFactory;
292
        }
293
        AlgorithmKeySpec viaCtor = invokeByteArrayConstructor(specType, material);
294 2 1. importSpec : removed conditional - replaced equality check with true → TIMED_OUT
2. importSpec : removed conditional - replaced equality check with false → KILLED
        if (viaCtor != null) {
295 1 1. importSpec : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::importSpec → KILLED
            return viaCtor;
296
        }
297
        throw new GeneralSecurityException("Cannot construct " + specType.getName() + " for algorithm '" + algId
298
                + "'; no fromRaw(byte[]), of(byte[]) or byte[] constructor accepted the key material");
299
    }
300
301
    /**
302
     * Invokes a static single-{@code byte[]} factory method named {@code name} on
303
     * {@code specType}, returning {@code null} when the method is absent.
304
     *
305
     * @param specType spec class to invoke on
306
     * @param name     factory method name
307
     * @param material encoded key bytes
308
     * @return the constructed spec, or {@code null} if no such method exists
309
     * @throws GeneralSecurityException if the method exists but fails to construct
310
     *                                  a spec
311
     */
312
    private static AlgorithmKeySpec invokeStaticFactory(Class<? extends AlgorithmKeySpec> specType, String name,
313
            byte[] material) throws GeneralSecurityException {
314
        try {
315
            Method method = specType.getMethod(name, byte[].class);
316 1 1. invokeStaticFactory : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::invokeStaticFactory → NO_COVERAGE
            return (AlgorithmKeySpec) method.invoke(null, (Object) material);
317
        } catch (NoSuchMethodException absent) {
318
            return null;
319
        } catch (IllegalAccessException | InvocationTargetException e) {
320
            throw new GeneralSecurityException("Factory " + specType.getName() + "." + name
321
                    + "(byte[]) failed", unwrap(e));
322
        }
323
    }
324
325
    /**
326
     * Invokes a single-{@code byte[]} constructor on {@code specType}, returning
327
     * {@code null} when no such constructor exists.
328
     *
329
     * @param specType spec class to instantiate
330
     * @param material encoded key bytes
331
     * @return the constructed spec, or {@code null} if no such constructor exists
332
     * @throws GeneralSecurityException if the constructor exists but fails
333
     */
334
    private static AlgorithmKeySpec invokeByteArrayConstructor(Class<? extends AlgorithmKeySpec> specType,
335
            byte[] material) throws GeneralSecurityException {
336
        try {
337
            Constructor<? extends AlgorithmKeySpec> ctor = specType.getConstructor(byte[].class);
338 1 1. invokeByteArrayConstructor : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::invokeByteArrayConstructor → KILLED
            return ctor.newInstance((Object) material);
339
        } catch (NoSuchMethodException absent) {
340
            return null;
341
        } catch (ReflectiveOperationException e) {
342
            throw new GeneralSecurityException("Constructor " + specType.getName() + "(byte[]) failed", unwrap(e));
343
        }
344
    }
345
346
    /**
347
     * Unwraps an {@link InvocationTargetException} to its underlying cause so the
348
     * thrown {@link GeneralSecurityException} carries the real failure.
349
     *
350
     * @param e reflective exception
351
     * @return the underlying cause when present, otherwise {@code e}
352
     */
353
    private static Throwable unwrap(ReflectiveOperationException e) {
354 5 1. unwrap : removed conditional - replaced equality check with true → NO_COVERAGE
2. unwrap : removed conditional - replaced equality check with true → NO_COVERAGE
3. unwrap : removed conditional - replaced equality check with false → NO_COVERAGE
4. unwrap : replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::unwrap → NO_COVERAGE
5. unwrap : removed conditional - replaced equality check with false → NO_COVERAGE
        return e instanceof InvocationTargetException && e.getCause() != null ? e.getCause() : e;
355
    }
356
357
    /**
358
     * Creates the keyring file with owner-only permissions before it is written,
359
     * so a freshly generated keyring is never momentarily world-readable while it
360
     * holds a clear-text private key (closing the create-then-{@code chmod} race).
361
     *
362
     * <p>
363
     * Applies only to a newly created file; when the keyring already exists (a key
364
     * is being added to it) the file keeps its current permissions and
365
     * {@link #restrictPermissions(Path)} tightens them after the write. On file
366
     * systems without POSIX support the file is created normally and
367
     * {@link #restrictPermissions(Path)} logs a warning.
368
     * </p>
369
     *
370
     * @param keyringFile keyring file to create
371
     * @throws IOException if the file cannot be created
372
     */
373
    private static void createOwnerOnlyFile(Path keyringFile) throws IOException {
374 2 1. createOwnerOnlyFile : removed conditional - replaced equality check with true → SURVIVED
2. createOwnerOnlyFile : removed conditional - replaced equality check with false → KILLED
        if (Files.exists(keyringFile)) {
375
            return;
376
        }
377
        try {
378
            Files.createFile(keyringFile,
379
                    PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(OWNER_ONLY_PERMISSIONS)));
380
        } catch (UnsupportedOperationException notPosix) {
381
            Files.createFile(keyringFile);
382
        }
383
    }
384
385
    /**
386
     * Restricts the keyring file to owner read/write. On POSIX file systems this
387
     * applies {@code 0600}; on file systems without POSIX support (for example
388
     * Windows) a warning is logged because the private key is stored in clear
389
     * text and the operator must restrict access by other means.
390
     *
391
     * @param keyringFile file to lock down
392
     */
393
    private static void restrictPermissions(Path keyringFile) {
394
        try {
395
            Files.setPosixFilePermissions(keyringFile, PosixFilePermissions.fromString(OWNER_ONLY_PERMISSIONS));
396
        } catch (UnsupportedOperationException notPosix) {
397
            if (LOG.isLoggable(Level.WARNING)) {
398
                LOG.log(Level.WARNING, "Keyring {0} holds a clear-text private key but this file system does not "
399
                        + "support POSIX permissions; restrict access to your account manually (e.g. via NTFS ACLs).",
400
                        keyringFile);
401
            }
402
        } catch (IOException e) {
403
            if (LOG.isLoggable(Level.WARNING)) {
404
                LOG.log(Level.WARNING, "Failed to restrict permissions on keyring " + keyringFile
405
                        + "; verify it is readable only by you", e);
406
            }
407
        }
408
    }
409
}

Mutations

115

1.1
Location : supportedAlgorithmsHint
Killed by : none
replaced return value with "" for org/egothor/methodatlas/evidence/SigningKeyGenerator::supportedAlgorithmsHint → SURVIVED
Covering tests

152

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:rejectsUnsupportedAlgorithm()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : generate
Killed by : none
removed conditional - replaced equality check with false → TIMED_OUT

155

1.1
Location : generate
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : generate
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

3.3
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:rejectsUnsupportedAlgorithm()]
removed conditional - replaced equality check with false → KILLED

4.4
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:rejectsUnsupportedAlgorithm()]
removed conditional - replaced equality check with true → KILLED

160

1.1
Location : generate
Killed by : none
removed call to zeroecho/sdk/util/BouncyCastleActivator::init → TIMED_OUT

161

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:refusesToOverwriteExistingAliasWithoutFlag()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with true → KILLED

162

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:refusesToOverwriteExistingAliasWithoutFlag()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with true → KILLED

3.3
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:overwriteFlagReplacesExistingAlias()]
removed conditional - replaced equality check with true → KILLED

4.4
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:refusesToOverwriteExistingAliasWithoutFlag()]
removed conditional - replaced equality check with false → KILLED

175

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[test-template:generatesAndMaterialisesEachSupportedAlgorithm(java.lang.String)]/[test-template-invocation:#3]
removed call to zeroecho/core/storage/KeyringStore::putPublic → KILLED

176

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[test-template:generatesAndMaterialisesEachSupportedAlgorithm(java.lang.String)]/[test-template-invocation:#3]
removed call to zeroecho/core/storage/KeyringStore::putPrivate → KILLED

177

1.1
Location : generate
Killed by : none
removed call to org/egothor/methodatlas/evidence/SigningKeyGenerator::createOwnerOnlyFile → SURVIVED
Covering tests

178

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:refusesToOverwriteExistingAliasWithoutFlag()]
removed call to zeroecho/core/storage/KeyringStore::save → KILLED

179

1.1
Location : generate
Killed by : none
removed call to org/egothor/methodatlas/evidence/SigningKeyGenerator::restrictPermissions → SURVIVED
Covering tests

182

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:exportsPublicKeyAsStandardPem()]
removed call to org/egothor/methodatlas/evidence/SigningKeyGenerator::writePublicKeyPem → KILLED

184

1.1
Location : generate
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::generate → KILLED

206

1.1
Location : writePublicKeyPem
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:exportsPublicKeyAsStandardPem()]
removed call to org/bouncycastle/openssl/jcajce/JcaPEMWriter::writeObject → KILLED

218

1.1
Location : canonicalAlgorithm
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:defaultsToEd25519WhenAlgorithmIsNull()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : canonicalAlgorithm
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:rejectsUnsupportedAlgorithm()]
removed conditional - replaced equality check with true → KILLED

220

1.1
Location : canonicalAlgorithm
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:rejectsUnsupportedAlgorithm()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : canonicalAlgorithm
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with true → KILLED

224

1.1
Location : canonicalAlgorithm
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
replaced return value with "" for org/egothor/methodatlas/evidence/SigningKeyGenerator::canonicalAlgorithm → KILLED

244

1.1
Location : requireAsymmetric
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : requireAsymmetric
Killed by : none
removed conditional - replaced equality check with false → TIMED_OUT

247

1.1
Location : requireAsymmetric
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::requireAsymmetric → KILLED

263

1.1
Location : findSpecType
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[test-template:generatesAndMaterialisesEachSupportedAlgorithm(java.lang.String)]/[test-template-invocation:#3]
removed conditional - replaced equality check with true → KILLED

2.2
Location : findSpecType
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with false → KILLED

264

1.1
Location : findSpecType
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::findSpecType → KILLED

287

1.1
Location : importSpec
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : importSpec
Killed by : none
removed conditional - replaced equality check with false → TIMED_OUT

290

1.1
Location : importSpec
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : importSpec
Killed by : none
removed conditional - replaced equality check with false → TIMED_OUT

291

1.1
Location : importSpec
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::importSpec → NO_COVERAGE

294

1.1
Location : importSpec
Killed by : none
removed conditional - replaced equality check with true → TIMED_OUT

2.2
Location : importSpec
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
removed conditional - replaced equality check with false → KILLED

295

1.1
Location : importSpec
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::importSpec → KILLED

316

1.1
Location : invokeStaticFactory
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::invokeStaticFactory → NO_COVERAGE

338

1.1
Location : invokeByteArrayConstructor
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:acceptsAlgorithmCaseInsensitively()]
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::invokeByteArrayConstructor → KILLED

354

1.1
Location : unwrap
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

2.2
Location : unwrap
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

3.3
Location : unwrap
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

4.4
Location : unwrap
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/SigningKeyGenerator::unwrap → NO_COVERAGE

5.5
Location : unwrap
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

374

1.1
Location : createOwnerOnlyFile
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : createOwnerOnlyFile
Killed by : org.egothor.methodatlas.evidence.SigningKeyGeneratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.evidence.SigningKeyGeneratorTest]/[method:overwriteFlagReplacesExistingAlias()]
removed conditional - replaced equality check with false → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1