blob: 12c33d8cb4ab586eb1063cb65db69ee003b07df0 [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.accumulo.test.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.accumulo.core.cli.Help;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.DefaultConfiguration;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.conf.SiteConfiguration;
import org.apache.commons.io.FileExistsException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x500.style.RFC4519Style;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.gaul.modernizer_maven_annotations.SuppressModernizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.google.common.base.Predicate;
public class CertUtils {
private static final Logger log = LoggerFactory.getLogger(CertUtils.class);
static {
Security.addProvider(new BouncyCastleProvider());
}
static class Opts extends Help {
@Parameter(description = "generate-all | generate-local | generate-self-trusted",
required = true, arity = 1)
List<String> operation = null;
@Parameter(names = {"--local-keystore"}, description = "Target path for generated keystore")
String localKeystore = null;
@Parameter(names = {"--root-keystore"}, description = "Path to root truststore,"
+ " generated with generate-all, or used for signing with generate-local")
String rootKeystore = null;
@Parameter(names = {"--root-truststore"},
description = "Target path for generated public root truststore")
String truststore = null;
@Parameter(names = {"--keystore-type"}, description = "Type of keystore file to use")
String keystoreType = "JKS";
@Parameter(names = {"--root-keystore-password"}, description = "Password for root keystore,"
+ " falls back to --keystore-password if not provided")
String rootKeystorePassword = null;
@Parameter(names = {"--keystore-password"},
description = "Password used to encrypt keystores."
+ " If omitted, the instance-wide secret will be used. If specified, the"
+ " password must also be explicitly configured in Accumulo.")
String keystorePassword = null;
@Parameter(names = {"--truststore-password"},
description = "Password used to encrypt the truststore. If omitted, empty password is used")
String truststorePassword = "";
@Parameter(names = {"--key-name-prefix"}, description = "Prefix for names of generated keys")
String keyNamePrefix = CertUtils.class.getSimpleName();
@Parameter(names = {"--issuer-rdn"},
description = "RDN string for issuer, for example: 'c=US,o=My Organization,cn=My Name'")
String issuerDirString = "o=Apache Accumulo";
@Parameter(names = "--site-file", description = "Load configuration from the given site file")
public String siteFile = null;
@Parameter(names = "--signing-algorithm", description = "Algorithm used to sign certificates")
public String signingAlg = "SHA512WITHRSA";
@Parameter(names = "--encryption-algorithm",
description = "Algorithm used to encrypt private keys")
public String encryptionAlg = "RSA";
@Parameter(names = "--keysize", description = "Key size used by encryption algorithm")
public int keysize = 4096;
public AccumuloConfiguration getConfiguration() {
if (siteFile == null) {
return SiteConfiguration.getInstance();
} else {
return new AccumuloConfiguration() {
Configuration xml = new Configuration();
{
xml.addResource(new Path(siteFile));
}
@Override
public Iterator<Entry<String,String>> iterator() {
TreeMap<String,String> map = new TreeMap<>();
for (Entry<String,String> props : DefaultConfiguration.getInstance())
map.put(props.getKey(), props.getValue());
for (Entry<String,String> props : xml)
map.put(props.getKey(), props.getValue());
return map.entrySet().iterator();
}
@Override
public String get(Property property) {
String value = xml.get(property.getKey());
if (value != null)
return value;
return DefaultConfiguration.getInstance().get(property);
}
@Override
@SuppressModernizer
public void getProperties(Map<String,String> props, Predicate<String> filter) {
for (Entry<String,String> entry : this)
if (filter.apply(entry.getKey()))
props.put(entry.getKey(), entry.getValue());
}
};
}
}
}
public static void main(String[] args) throws Exception {
Opts opts = new Opts();
opts.parseArgs(CertUtils.class.getName(), args);
String operation = opts.operation.get(0);
String keyPassword = opts.keystorePassword;
if (keyPassword == null)
keyPassword = getDefaultKeyPassword();
String rootKeyPassword = opts.rootKeystorePassword;
if (rootKeyPassword == null) {
rootKeyPassword = keyPassword;
}
CertUtils certUtils = new CertUtils(opts.keystoreType, opts.issuerDirString, opts.encryptionAlg,
opts.keysize, opts.signingAlg);
if ("generate-all".equals(operation)) {
certUtils.createAll(new File(opts.rootKeystore), new File(opts.localKeystore),
new File(opts.truststore), opts.keyNamePrefix, rootKeyPassword, keyPassword,
opts.truststorePassword);
} else if ("generate-local".equals(operation)) {
certUtils.createSignedCert(new File(opts.localKeystore), opts.keyNamePrefix + "-local",
keyPassword, opts.rootKeystore, rootKeyPassword);
} else if ("generate-self-trusted".equals(operation)) {
certUtils.createSelfSignedCert(new File(opts.truststore), opts.keyNamePrefix + "-selfTrusted",
keyPassword);
} else {
JCommander jcommander = new JCommander(opts);
jcommander.setProgramName(CertUtils.class.getName());
jcommander.usage();
System.err.println("Unrecognized operation: " + opts.operation);
System.exit(0);
}
}
private static String getDefaultKeyPassword() {
return SiteConfiguration.getInstance().get(Property.INSTANCE_SECRET);
}
private String issuerDirString;
private String keystoreType;
private String encryptionAlgorithm;
private int keysize;
private String signingAlgorithm;
public CertUtils(String keystoreType, String issuerDirString, String encryptionAlgorithm,
int keysize, String signingAlgorithm) {
super();
this.keystoreType = keystoreType;
this.issuerDirString = issuerDirString;
this.encryptionAlgorithm = encryptionAlgorithm;
this.keysize = keysize;
this.signingAlgorithm = signingAlgorithm;
}
public void createAll(File rootKeystoreFile, File localKeystoreFile, File trustStoreFile,
String keyNamePrefix, String rootKeystorePassword, String keystorePassword,
String truststorePassword) throws KeyStoreException, CertificateException,
NoSuchAlgorithmException, IOException, OperatorCreationException, AccumuloSecurityException,
NoSuchProviderException, UnrecoverableKeyException, FileNotFoundException {
createSelfSignedCert(rootKeystoreFile, keyNamePrefix + "-root", rootKeystorePassword);
createSignedCert(localKeystoreFile, keyNamePrefix + "-local", keystorePassword,
rootKeystoreFile.getAbsolutePath(), rootKeystorePassword);
createPublicCert(trustStoreFile, keyNamePrefix + "-public", rootKeystoreFile.getAbsolutePath(),
rootKeystorePassword, truststorePassword);
}
public void createPublicCert(File targetKeystoreFile, String keyName, String rootKeystorePath,
String rootKeystorePassword, String truststorePassword)
throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException,
KeyStoreException, UnrecoverableKeyException {
KeyStore signerKeystore = KeyStore.getInstance(keystoreType);
char[] signerPasswordArray = rootKeystorePassword.toCharArray();
try (FileInputStream fis = new FileInputStream(rootKeystorePath)) {
signerKeystore.load(fis, signerPasswordArray);
}
Certificate rootCert = findCert(signerKeystore);
KeyStore keystore = KeyStore.getInstance(keystoreType);
keystore.load(null, null);
keystore.setCertificateEntry(keyName + "Cert", rootCert);
try (FileOutputStream fos = new FileOutputStream(targetKeystoreFile)) {
keystore.store(fos, truststorePassword.toCharArray());
}
}
public void createSignedCert(File targetKeystoreFile, String keyName, String keystorePassword,
String signerKeystorePath, String signerKeystorePassword) throws KeyStoreException,
CertificateException, NoSuchAlgorithmException, IOException, OperatorCreationException,
AccumuloSecurityException, UnrecoverableKeyException, NoSuchProviderException {
KeyStore signerKeystore = KeyStore.getInstance(keystoreType);
char[] signerPasswordArray = signerKeystorePassword.toCharArray();
try (FileInputStream fis = new FileInputStream(signerKeystorePath)) {
signerKeystore.load(fis, signerPasswordArray);
}
Certificate signerCert = findCert(signerKeystore);
PrivateKey signerKey = findPrivateKey(signerKeystore, signerPasswordArray);
KeyPair kp = generateKeyPair();
Certificate cert = generateCert(keyName, kp, false, signerCert.getPublicKey(), signerKey);
char[] password = keystorePassword.toCharArray();
KeyStore keystore = KeyStore.getInstance(keystoreType);
keystore.load(null, null);
keystore.setCertificateEntry(keyName + "Cert", cert);
keystore.setKeyEntry(keyName + "Key", kp.getPrivate(), password,
new Certificate[] {cert, signerCert});
try (FileOutputStream fos = new FileOutputStream(targetKeystoreFile)) {
keystore.store(fos, password);
}
}
public void createSelfSignedCert(File targetKeystoreFile, String keyName, String keystorePassword)
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
OperatorCreationException, AccumuloSecurityException, NoSuchProviderException {
if (targetKeystoreFile.exists()) {
throw new FileExistsException(targetKeystoreFile);
}
KeyPair kp = generateKeyPair();
Certificate cert = generateCert(keyName, kp, true, kp.getPublic(), kp.getPrivate());
char[] password = keystorePassword.toCharArray();
KeyStore keystore = KeyStore.getInstance(keystoreType);
keystore.load(null, null);
keystore.setCertificateEntry(keyName + "Cert", cert);
keystore.setKeyEntry(keyName + "Key", kp.getPrivate(), password, new Certificate[] {cert});
try (FileOutputStream fos = new FileOutputStream(targetKeystoreFile)) {
keystore.store(fos, password);
}
}
private KeyPair generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {
KeyPairGenerator gen = KeyPairGenerator.getInstance(encryptionAlgorithm);
gen.initialize(keysize);
return gen.generateKeyPair();
}
private Certificate generateCert(String keyName, KeyPair kp, boolean isCertAuthority,
PublicKey signerPublicKey, PrivateKey signerPrivateKey) throws IOException, CertIOException,
OperatorCreationException, CertificateException, NoSuchAlgorithmException {
Calendar startDate = Calendar.getInstance();
Calendar endDate = Calendar.getInstance();
endDate.add(Calendar.YEAR, 100);
BigInteger serialNumber = BigInteger.valueOf((startDate.getTimeInMillis()));
X500Name issuer =
new X500Name(IETFUtils.rDNsFromString(issuerDirString, RFC4519Style.INSTANCE));
JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(issuer, serialNumber,
startDate.getTime(), endDate.getTime(), issuer, kp.getPublic());
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
certGen.addExtension(Extension.subjectKeyIdentifier, false,
extensionUtils.createSubjectKeyIdentifier(kp.getPublic()));
certGen.addExtension(Extension.basicConstraints, false, new BasicConstraints(isCertAuthority));
certGen.addExtension(Extension.authorityKeyIdentifier, false,
extensionUtils.createAuthorityKeyIdentifier(signerPublicKey));
if (isCertAuthority) {
certGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
}
X509CertificateHolder cert =
certGen.build(new JcaContentSignerBuilder(signingAlgorithm).build(signerPrivateKey));
return new JcaX509CertificateConverter().getCertificate(cert);
}
static Certificate findCert(KeyStore keyStore) throws KeyStoreException {
Enumeration<String> aliases = keyStore.aliases();
Certificate cert = null;
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (keyStore.isCertificateEntry(alias)) {
if (cert == null) {
cert = keyStore.getCertificate(alias);
} else {
log.warn("Found multiple certificates in keystore. Ignoring " + alias);
}
}
}
if (cert == null) {
throw new KeyStoreException("Could not find cert in keystore");
}
return cert;
}
static PrivateKey findPrivateKey(KeyStore keyStore, char[] keystorePassword)
throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException {
Enumeration<String> aliases = keyStore.aliases();
PrivateKey key = null;
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (keyStore.isKeyEntry(alias)) {
if (key == null) {
key = (PrivateKey) keyStore.getKey(alias, keystorePassword);
} else {
log.warn("Found multiple keys in keystore. Ignoring " + alias);
}
}
}
if (key == null) {
throw new KeyStoreException("Could not find private key in keystore");
}
return key;
}
}