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