| 1 | /*- | |
| 2 | * #%L | |
| 3 | * io.earcam.utilitarian.security | |
| 4 | * %% | |
| 5 | * Copyright (C) 2017 - 2018 earcam | |
| 6 | * %% | |
| 7 | * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT) | |
| 8 | * | |
| 9 | * You <b>must</b> choose to accept, in full - any individual or combination of | |
| 10 | * the following licenses: | |
| 11 | * <ul> | |
| 12 | * <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li> | |
| 13 | * <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li> | |
| 14 | * <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li> | |
| 15 | * <li><a href="https://opensource.org/licenses/MIT">MIT</a></li> | |
| 16 | * </ul> | |
| 17 | * #L% | |
| 18 | */ | |
| 19 | package io.earcam.utilitarian.security; | |
| 20 | ||
| 21 | import java.io.StringWriter; | |
| 22 | import java.io.Writer; | |
| 23 | import java.math.BigInteger; | |
| 24 | import java.security.KeyPair; | |
| 25 | import java.security.cert.X509Certificate; | |
| 26 | import java.time.LocalDate; | |
| 27 | import java.time.ZoneId; | |
| 28 | import java.util.Date; | |
| 29 | import java.util.Objects; | |
| 30 | import java.util.concurrent.TimeUnit; | |
| 31 | ||
| 32 | import javax.annotation.ParametersAreNonnullByDefault; | |
| 33 | ||
| 34 | import org.bouncycastle.asn1.ASN1ObjectIdentifier; | |
| 35 | import org.bouncycastle.asn1.x500.X500Name; | |
| 36 | import org.bouncycastle.asn1.x509.BasicConstraints; | |
| 37 | import org.bouncycastle.cert.CertIOException; | |
| 38 | import org.bouncycastle.cert.X509CertificateHolder; | |
| 39 | import org.bouncycastle.cert.X509v3CertificateBuilder; | |
| 40 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | |
| 41 | import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; | |
| 42 | import org.bouncycastle.jce.X509KeyUsage; | |
| 43 | import org.bouncycastle.jce.provider.BouncyCastleProvider; | |
| 44 | import org.bouncycastle.openssl.jcajce.JcaPEMWriter; | |
| 45 | import org.bouncycastle.operator.ContentSigner; | |
| 46 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; | |
| 47 | ||
| 48 | import io.earcam.unexceptional.Closing; | |
| 49 | import io.earcam.unexceptional.Exceptional; | |
| 50 | ||
| 51 | @ParametersAreNonnullByDefault | |
| 52 | public class Certificates { | |
| 53 | ||
| 54 | private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider(); | |
| 55 | static final String DN_LOCALHOST = "DN=localhost, L=London, C=GB"; | |
| 56 | ||
| 57 | public static class CertificateBuilder { | |
| 58 | ||
| 59 | @SuppressWarnings("squid:S1313") // SonarQube false-positive; not an IP address | |
| 60 | private static final String EXTENSION_KEY_USAGE = "2.5.29.15"; | |
| 61 | @SuppressWarnings("squid:S1313") // SonarQube false-positive; not an IP address | |
| 62 | static final String EXTENSION_MAY_ACT_AS_CA = "2.5.29.19"; | |
| 63 | private String issuerName = "acme"; | |
| 64 | private String subjectName; | |
| 65 | private BigInteger serial = BigInteger.ONE; | |
| 66 | private boolean canSignOtherCertificates = false; | |
| 67 | private LocalDate validFrom = LocalDate.now(ZoneId.systemDefault()); | |
| 68 | private long duration = 365; | |
| 69 | private TimeUnit unit = TimeUnit.DAYS; | |
| 70 | private String signatureAlgorithm = "SHA256withRSA"; | |
| 71 | private KeyPair keyPair; | |
| 72 | ||
| 73 | ||
| 74 | CertificateBuilder() | |
| 75 | {} | |
| 76 | ||
| 77 | ||
| 78 | public CertificateBuilder issuer(String name) | |
| 79 | { | |
| 80 | issuerName = name; | |
| 81 |
1
1. issuer : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::issuer to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 82 | } | |
| 83 | ||
| 84 | ||
| 85 | public CertificateBuilder subject(String name) | |
| 86 | { | |
| 87 | subjectName = name; | |
| 88 |
1
1. subject : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::subject to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 89 | } | |
| 90 | ||
| 91 | ||
| 92 | public CertificateBuilder serial(int number) | |
| 93 | { | |
| 94 |
1
1. serial : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::serial to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return serial(BigInteger.valueOf(number)); |
| 95 | } | |
| 96 | ||
| 97 | ||
| 98 | public CertificateBuilder serial(BigInteger number) | |
| 99 | { | |
| 100 | this.serial = number; | |
| 101 |
1
1. serial : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::serial to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 102 | } | |
| 103 | ||
| 104 | ||
| 105 | public CertificateBuilder canSignOtherCertificates() | |
| 106 | { | |
| 107 | canSignOtherCertificates = true; | |
| 108 |
1
1. canSignOtherCertificates : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::canSignOtherCertificates to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 109 | } | |
| 110 | ||
| 111 | ||
| 112 | public CertificateBuilder key(KeyPair pair) | |
| 113 | { | |
| 114 | this.keyPair = pair; | |
| 115 |
1
1. key : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::key to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 116 | } | |
| 117 | ||
| 118 | ||
| 119 | public CertificateBuilder signedBy(String signatureAlgorithm) | |
| 120 | { | |
| 121 | this.signatureAlgorithm = signatureAlgorithm; | |
| 122 |
1
1. signedBy : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::signedBy to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 123 | } | |
| 124 | ||
| 125 | ||
| 126 | public CertificateBuilder validFrom(LocalDate from) | |
| 127 | { | |
| 128 | validFrom = from; | |
| 129 |
1
1. validFrom : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::validFrom to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 130 | } | |
| 131 | ||
| 132 | ||
| 133 | public CertificateBuilder validFor(long duration, TimeUnit unit) | |
| 134 | { | |
| 135 | this.duration = duration; | |
| 136 | this.unit = unit; | |
| 137 |
1
1. validFor : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::validFor to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return this; |
| 138 | } | |
| 139 | ||
| 140 | ||
| 141 | public X509Certificate toX509() | |
| 142 | { | |
| 143 | Objects.requireNonNull(keyPair, "keyPair"); | |
| 144 | Objects.requireNonNull(issuerName, "issuerName"); | |
| 145 | Objects.requireNonNull(subjectName, "subjectName"); | |
| 146 | X500Name issuer = new X500Name(addCnIfMissing(issuerName)); | |
| 147 | X500Name subject = new X500Name(addCnIfMissing(subjectName)); | |
| 148 | ||
| 149 | Date from = javaDate(validFrom); | |
| 150 |
1
1. toX509 : Replaced long addition with subtraction → KILLED |
Date to = new Date(from.getTime() + unit.toMillis(duration)); |
| 151 | ||
| 152 | X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( | |
| 153 | issuer, | |
| 154 | serial, | |
| 155 | from, | |
| 156 | to, | |
| 157 | subject, | |
| 158 | keyPair.getPublic()); | |
| 159 | ||
| 160 |
1
1. toX509 : removed call to io/earcam/unexceptional/Exceptional::accept → KILLED |
Exceptional.accept(this::addExtensions, certificateBuilder); |
| 161 | ||
| 162 | X509CertificateHolder signed = sign(keyPair, signatureAlgorithm, certificateBuilder); | |
| 163 | ||
| 164 | JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(PROVIDER); | |
| 165 |
1
1. toX509 : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::toX509 to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return Exceptional.apply(converter::getCertificate, signed); |
| 166 | } | |
| 167 | ||
| 168 | ||
| 169 | static Date javaDate(LocalDate date) | |
| 170 | { | |
| 171 |
1
1. javaDate : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::javaDate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return java.sql.Date.valueOf(date); |
| 172 | } | |
| 173 | ||
| 174 | ||
| 175 | static LocalDate localDate(Date date) | |
| 176 | { | |
| 177 |
1
1. localDate : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::localDate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new java.sql.Date(date.getTime()).toLocalDate(); |
| 178 | } | |
| 179 | ||
| 180 | ||
| 181 | private void addExtensions(X509v3CertificateBuilder certificateBuilder) throws CertIOException | |
| 182 | { | |
| 183 | certificateBuilder.addExtension( | |
| 184 | new ASN1ObjectIdentifier(EXTENSION_MAY_ACT_AS_CA), | |
| 185 | false, | |
| 186 | new BasicConstraints(canSignOtherCertificates)).addExtension( | |
| 187 | new ASN1ObjectIdentifier(EXTENSION_KEY_USAGE), | |
| 188 | true, | |
| 189 | new X509KeyUsage( | |
| 190 | X509KeyUsage.digitalSignature | | |
| 191 | X509KeyUsage.nonRepudiation | | |
| 192 | X509KeyUsage.keyEncipherment | | |
| 193 | X509KeyUsage.dataEncipherment)); | |
| 194 | } | |
| 195 | ||
| 196 | ||
| 197 | private String addCnIfMissing(String name) | |
| 198 | { | |
| 199 |
2
1. addCnIfMissing : negated conditional → KILLED 2. addCnIfMissing : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::addCnIfMissing to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return (name.indexOf('=') == -1) ? "CN=" + name : name; |
| 200 | } | |
| 201 | ||
| 202 | ||
| 203 | private static X509CertificateHolder sign(KeyPair keyPair, String signatureAlgorithm, X509v3CertificateBuilder certificateBuilder) | |
| 204 | { | |
| 205 | JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signatureAlgorithm); | |
| 206 | ContentSigner sigGen = Exceptional.apply(jcaContentSignerBuilder::build, keyPair.getPrivate()); | |
| 207 |
1
1. sign : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::sign to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return certificateBuilder.build(sigGen); |
| 208 | } | |
| 209 | ||
| 210 | ||
| 211 | public String toPem() | |
| 212 | { | |
| 213 | StringWriter writer = new StringWriter(); | |
| 214 |
1
1. toPem : removed call to io/earcam/utilitarian/security/Certificates$CertificateBuilder::toPem → KILLED |
toPem(writer); |
| 215 |
1
1. toPem : mutated return of Object value for io/earcam/utilitarian/security/Certificates$CertificateBuilder::toPem to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return writer.toString(); |
| 216 | } | |
| 217 | ||
| 218 | ||
| 219 | public void toPem(Writer writer) | |
| 220 | { | |
| 221 |
1
1. toPem : removed call to io/earcam/unexceptional/Closing::closeAfterAccepting → KILLED |
Closing.closeAfterAccepting(JcaPEMWriter::new, writer, toX509(), JcaPEMWriter::writeObject); |
| 222 | } | |
| 223 | ||
| 224 | } | |
| 225 | ||
| 226 | ||
| 227 | private Certificates() | |
| 228 | {} | |
| 229 | ||
| 230 | ||
| 231 | public static CertificateBuilder certificate(KeyPair pair, String subjectName) | |
| 232 | { | |
| 233 |
1
1. certificate : mutated return of Object value for io/earcam/utilitarian/security/Certificates::certificate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return certificate(pair) |
| 234 | .subject(subjectName); | |
| 235 | } | |
| 236 | ||
| 237 | ||
| 238 | public static CertificateBuilder certificate(KeyPair pair) | |
| 239 | { | |
| 240 |
1
1. certificate : mutated return of Object value for io/earcam/utilitarian/security/Certificates::certificate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return certificate().key(pair); |
| 241 | } | |
| 242 | ||
| 243 | ||
| 244 | public static CertificateBuilder certificate() | |
| 245 | { | |
| 246 |
1
1. certificate : mutated return of Object value for io/earcam/utilitarian/security/Certificates::certificate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new CertificateBuilder(); |
| 247 | } | |
| 248 | ||
| 249 | ||
| 250 | public static X509Certificate localhostCertificate(KeyPair keys) | |
| 251 | { | |
| 252 |
1
1. localhostCertificate : mutated return of Object value for io/earcam/utilitarian/security/Certificates::localhostCertificate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return hostCertificate(keys, DN_LOCALHOST); |
| 253 | } | |
| 254 | ||
| 255 | ||
| 256 | /** | |
| 257 | * @deprecated | |
| 258 | * @see #localhostCertificate(KeyPair) | |
| 259 | */ | |
| 260 | @Deprecated | |
| 261 | public static X509Certificate hostCertificate(KeyPair keys) | |
| 262 | { | |
| 263 | throw new UnsupportedOperationException(); | |
| 264 | } | |
| 265 | ||
| 266 | ||
| 267 | public static X509Certificate hostCertificate(KeyPair keys, String hostname) | |
| 268 | { | |
| 269 |
1
1. hostCertificate : mutated return of Object value for io/earcam/utilitarian/security/Certificates::hostCertificate to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return certificate(keys) |
| 270 | .subject(hostname) | |
| 271 | .toX509(); | |
| 272 | } | |
| 273 | } | |
Mutations | ||
| 81 |
1.1 |
|
| 88 |
1.1 |
|
| 94 |
1.1 |
|
| 101 |
1.1 |
|
| 108 |
1.1 |
|
| 115 |
1.1 |
|
| 122 |
1.1 |
|
| 129 |
1.1 |
|
| 137 |
1.1 |
|
| 150 |
1.1 |
|
| 160 |
1.1 |
|
| 165 |
1.1 |
|
| 171 |
1.1 |
|
| 177 |
1.1 |
|
| 199 |
1.1 2.2 |
|
| 207 |
1.1 |
|
| 214 |
1.1 |
|
| 215 |
1.1 |
|
| 221 |
1.1 |
|
| 233 |
1.1 |
|
| 240 |
1.1 |
|
| 246 |
1.1 |
|
| 252 |
1.1 |
|
| 269 |
1.1 |