| /* |
| * 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"); |
| } |
| } |
| } |