blob: 0dd226ec3a7dd8a5c4368423abb61c0c99b0c8ca [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.cache.ssl;
import static java.util.stream.Collectors.toList;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
/**
* Class which allows easily building certificates. It can also be used to build
* Certificate Authorities. The class is intended to be used in conjunction with {@link CertStores}
* to facilitate building key and trust stores.
*/
public class CertificateBuilder {
private final int days;
private final String algorithm;
private X500Name name;
private List<String> dnsNames;
private List<InetAddress> ipAddresses;
private boolean isCA;
private CertificateMaterial issuer;
public CertificateBuilder() {
this(30, "SHA256withRSA");
}
public CertificateBuilder(int days, String algorithm) {
this.days = days;
this.algorithm = algorithm;
this.dnsNames = new ArrayList<>();
this.ipAddresses = new ArrayList<>();
}
private static GeneralName dnsGeneralName(String name) {
return new GeneralName(GeneralName.dNSName, name);
}
private static GeneralName ipGeneralName(InetAddress hostAddress) {
return new GeneralName(GeneralName.iPAddress,
new DEROctetString(hostAddress.getAddress()));
}
public CertificateBuilder commonName(String cn) {
this.name = new X500Name("CN=" + cn + ", O=Geode");
return this;
}
public CertificateBuilder sanDnsName(String hostName) {
this.dnsNames.add(hostName);
return this;
}
public CertificateBuilder sanIpAddress(InetAddress hostAddress) {
this.ipAddresses.add(hostAddress);
return this;
}
public CertificateBuilder isCA() {
this.isCA = true;
return this;
}
public CertificateBuilder issuedBy(CertificateMaterial issuer) {
this.issuer = issuer;
return this;
}
private byte[] san() throws IOException {
List<GeneralName> names = dnsNames.stream()
.map(CertificateBuilder::dnsGeneralName)
.collect(toList());
names.addAll(ipAddresses.stream()
.map(CertificateBuilder::ipGeneralName)
.collect(toList()));
return names.isEmpty() ? null
: new GeneralNames(names.toArray(new GeneralName[] {})).getEncoded();
}
public CertificateMaterial generate() {
KeyPair keyPair = generateKeyPair("RSA");
PrivateKey privateKey;
X509Certificate issuerCertificate = null;
if (issuer == null) {
privateKey = keyPair.getPrivate();
} else {
privateKey = issuer.getPrivateKey();
}
X509Certificate cert = generate(keyPair.getPublic(), privateKey);
if (issuer != null) {
issuerCertificate = issuer.getCertificate();
}
return new CertificateMaterial(cert, keyPair, issuerCertificate);
}
private X509Certificate generate(PublicKey publicKey, PrivateKey privateKey) {
try {
AlgorithmIdentifier sigAlgId =
new DefaultSignatureAlgorithmIdentifierFinder().find(algorithm);
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
AsymmetricKeyParameter publicKeyAsymKeyParam =
PublicKeyFactory.createKey(publicKey.getEncoded());
AsymmetricKeyParameter privateKeyAsymKeyParam =
PrivateKeyFactory.createKey(privateKey.getEncoded());
SubjectPublicKeyInfo subPubKeyInfo =
SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
ContentSigner sigGen =
new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKeyAsymKeyParam);
Date from = new Date();
Date to = new Date(from.getTime() + days * 86400000L);
BigInteger sn = new BigInteger(64, new SecureRandom());
X509v3CertificateBuilder v3CertGen;
if (issuer == null) {
// This is a self-signed certificate
v3CertGen = new X509v3CertificateBuilder(name, sn, from, to, name, subPubKeyInfo);
} else {
v3CertGen = new X509v3CertificateBuilder(
new X500Name(issuer.getCertificate().getIssuerDN().getName()),
sn, from, to, name, subPubKeyInfo);
}
byte[] subjectAltName = san();
if (subjectAltName != null) {
v3CertGen.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
}
if (isCA) {
v3CertGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
v3CertGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
}
// Not strictly necessary, but this makes the certs look like those generated
// by `keytool`.
SubjectKeyIdentifier subjectKeyIdentifier =
new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKeyAsymKeyParam)
.getEncoded());
v3CertGen.addExtension(Extension.subjectKeyIdentifier, false, subjectKeyIdentifier);
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
return new JcaX509CertificateConverter()
.setProvider(new BouncyCastleProvider())
.getCertificate(certificateHolder);
} catch (Exception e) {
throw new RuntimeException("Unable to create certificate", e);
}
}
private KeyPair generateKeyPair(String algorithm) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
keyGen.initialize(2048);
return keyGen.genKeyPair();
} catch (NoSuchAlgorithmException nex) {
throw new RuntimeException("Unable to generate " + algorithm + " keypair");
}
}
}