/*
* Copyright (c) 2013 Mike Heath. All rights reserved.
*
* Licensed 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 cloudeventbus.cli;
import cloudeventbus.Subject;
import cloudeventbus.pki.Certificate;
import cloudeventbus.pki.CertificateChain;
import cloudeventbus.pki.CertificateStoreLoader;
import cloudeventbus.pki.CertificateUtils;
import cloudeventbus.pki.TrustStore;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import org.apache.commons.codec.binary.Base64InputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author Mike Heath <elcapo@gmail.com>
*/
// TODO Write and publish docs
public class Certs {
private static final String CHAIN_CERTIFICATE = "chain-certificate";
private static final String CREATE_AUTHORITY = "create-authority";
private static final String CREATE_CLIENT = "create-client";
private static final String CREATE_SERVER = "create-server";
private static final String IMPORT_CERTIFICATES = "import-certificates";
private static final String LIST_AUTHORITIES = "list-authorities";
private static final String SHOW_CERTIFICATE = "show-certificate";
private static final String VALIDATE_CERTIFICATE = "validate-certificate";
private static final String REMOVE_AUTHORITY = "remove-authority";
public static void main(String[] args) throws Exception {
final JCommander commander = new JCommander();
final DefaultOptions options = new DefaultOptions();
final CreateAuthorityCommand createAuthorityCommand = new CreateAuthorityCommand();
final CreateClientCommand createClientCommand = new CreateClientCommand();
final CreateServerCommand createServerCommand = new CreateServerCommand();
final ChainCertificateCommand chainCertificateCommand = new ChainCertificateCommand();
final ShowCertificateCommand showCertificateCommand = new ShowCertificateCommand();
final ImportCertificatesCommand importCertificatesCommand = new ImportCertificatesCommand();
final RemoveAuthorityCommand removeAuthorityCommand = new RemoveAuthorityCommand();
final ValidateCommand validateCommand = new ValidateCommand();
commander.addObject(options);
commander.addCommand(createAuthorityCommand);
commander.addCommand(createClientCommand);
commander.addCommand(createServerCommand);
commander.addCommand(chainCertificateCommand);
commander.addCommand(new ListAuthorities());
commander.addCommand(showCertificateCommand);
commander.addCommand(importCertificatesCommand);
commander.addCommand(removeAuthorityCommand);
commander.addCommand(validateCommand);
commander.setProgramName("eventbus-certs");
try {
commander.parse(args);
final String command = commander.getParsedCommand();
if (command == null) {
commander.usage();
} else {
final TrustStore trustStore = CertificateUtils.loadTrustStore(options.trustStore);
switch(command) {
case CREATE_AUTHORITY: {
final KeyPair keyPair = CertificateUtils.generateKeyPair();
CertificateUtils.savePrivateKey(keyPair.getPrivate(), createAuthorityCommand.privateKey);
final Certificate certificate = CertificateUtils.generateSelfSignedCertificate(
keyPair,
getExpirationDate(createAuthorityCommand.expirationDate),
Subject.list(createAuthorityCommand.subscribePermissions),
Subject.list(createAuthorityCommand.publishPermissions),
createAuthorityCommand.comment
);
trustStore.add(certificate);
CertificateUtils.saveCertificates(options.trustStore, trustStore);
System.out.println("Created authority certificate.");
break;
}
case LIST_AUTHORITIES: {
displayCertificates(trustStore);
break;
}
case CREATE_CLIENT:
createCertificate(trustStore, Certificate.Type.CLIENT, createClientCommand);
break;
case CREATE_SERVER:
createCertificate(trustStore, Certificate.Type.SERVER, createServerCommand);
break;
case SHOW_CERTIFICATE: {
final CertificateChain certificates = CertificateUtils.loadCertificateChain(showCertificateCommand.certificate);
displayCertificates(certificates);
break;
}
case CHAIN_CERTIFICATE:
chainCertificate(chainCertificateCommand);
break;
case VALIDATE_CERTIFICATE: {
final CertificateChain certificates = CertificateUtils.loadCertificateChain(validateCommand.certificate);
trustStore.validateCertificateChain(certificates);
System.out.println(validateCommand.certificate + " is valid.");
break;
}
case IMPORT_CERTIFICATES: {
final Path path = Paths.get(importCertificatesCommand.certificate);
try (
final InputStream fileIn = Files.newInputStream(path);
final InputStream in = new Base64InputStream(fileIn)
) {
final Collection<Certificate> certificates = new ArrayList<>();
CertificateStoreLoader.load(in, certificates);
for (Certificate certificate : certificates) {
trustStore.add(certificate);
}
CertificateUtils.saveCertificates(options.trustStore, trustStore);
}
break;
}
case REMOVE_AUTHORITY:
if (trustStore.remove(removeAuthorityCommand.serialNumber)) {
System.err.println("Removed certificate from trust store.");
CertificateUtils.saveCertificates(options.trustStore, trustStore);
} else {
System.err.println("Certificate with serial number " + removeAuthorityCommand.serialNumber + " not found.");
}
}
}
} catch (ParameterException e) {
System.err.println(e.getMessage());
commander.usage(commander.getParsedCommand());
System.exit(1);
}
}
private static void displayCertificates(Iterable<Certificate> certificates) {
System.out.println("=============================================================");
for (Certificate certificate : certificates) {
System.out.println("Serial number: " + certificate.getSerialNumber());
System.out.println("Issuer: " + certificate.getIssuer());
System.out.println("Type: " + certificate.getType());
final String expirationString;
if (certificate.getExpirationDate() == -1) {
expirationString = "Never";
} else if (certificate.getExpirationDate() < System.currentTimeMillis()) {
expirationString = "Expired";
} else {
final long seconds = (certificate.getExpirationDate() - System.currentTimeMillis()) / 1000;
expirationString = "Expires in " + formatTime(seconds);
}
System.out.println("Expiration: " + expirationString);
System.out.println("Publish permissions: " + certificate.getPublishPermissions());
System.out.println("Subscribe permissions: " + certificate.getSubscribePermissions());
System.out.println("Comment: " + certificate.getComment());
System.out.println("=============================================================");
}
}
private static String formatTime(long seconds) {
final long days = seconds / TimeUnit.DAYS.toSeconds(1);
seconds -= TimeUnit.DAYS.toSeconds(days);
final long hours = seconds / TimeUnit.HOURS.toSeconds(1);
seconds -= TimeUnit.HOURS.toSeconds(hours);
final long minutes = seconds / TimeUnit.MINUTES.toSeconds(1);
seconds -= TimeUnit.MINUTES.toSeconds(minutes);
return String.format("%dd:%dh:%dm:%ds", days, hours, minutes,seconds);
}
private static void createCertificate(TrustStore trustStore, Certificate.Type type, AbstractCreateClientServerCommand createCommand) throws Exception {
final Certificate issuerCertificate = trustStore.get(createCommand.issuer);
if (issuerCertificate == null) {
throw new IllegalArgumentException("No certificate found in trust store with serial number " + createCommand.issuer);
}
final PrivateKey issuerPrivateKey = CertificateUtils.loadPrivateKey(createCommand.issuerPrivateKey);
final KeyPair keyPair = CertificateUtils.generateKeyPair();
CertificateUtils.savePrivateKey(keyPair.getPrivate(), createCommand.privateKey);
final Certificate certificate = CertificateUtils.generateSignedCertificate(
issuerCertificate,
issuerPrivateKey,
keyPair.getPublic(),
type,
getExpirationDate(createCommand.expirationDate),
Subject.list(createCommand.subscribePermissions),
Subject.list(createCommand.publishPermissions),
createCommand.comment
);
final CertificateChain chain = new CertificateChain(certificate);
CertificateUtils.saveCertificates(createCommand.certificate, chain);
}
private static void chainCertificate(ChainCertificateCommand command) throws Exception {
final CertificateChain certificates = CertificateUtils.loadCertificateChain(command.existingCertificate);
final PrivateKey privateKey = CertificateUtils.loadPrivateKey(command.existingPrivateKey);
final KeyPair keyPair = CertificateUtils.generateKeyPair();
final Certificate certificate = CertificateUtils.generateSignedCertificate(
certificates.getLast(),
privateKey,
keyPair.getPublic(),
certificates.getLast().getType(),
getExpirationDate(command.expirationDate),
Subject.list(command.subscribePermissions),
Subject.list(command.publishPermissions),
command.comment
);
certificates.add(certificate);
CertificateUtils.saveCertificates(command.certificate, certificates);
CertificateUtils.savePrivateKey(keyPair.getPrivate(), command.privateKey);
}
private static long getExpirationDate(long expirationDate) {
return expirationDate == -1 ? -1 : System.currentTimeMillis() + expirationDate * 1000;
}
private static class AbstractCreateCommand {
@Parameter(names = "-privateKey", description = "The file to store the private key for the certificate being created.", required = true)
String privateKey;
@Parameter(names = "-expirationDate", description = "The time, in seconds, until the certifcate expires. Use -1 for no expiration.")
long expirationDate = -1;
@Parameter(names = "-comment", description = "A comment to associate with the certificate.")
String comment;
@Parameter(names = "-publishPermission", description = "Specifies a subject on which the certificate is authorized to publish.")
List<String> publishPermissions = Arrays.asList("*");
@Parameter(names = "-subscribePermission", description = "Specifies a subject on which the certificate is authorized to subscribe.")
List<String> subscribePermissions = Arrays.asList("*");
}
@Parameters(commandNames = CREATE_AUTHORITY, commandDescription = "Creates an authority certificate and adds it to the trust store")
private static class CreateAuthorityCommand extends AbstractCreateCommand {
}
private static abstract class AbstractCreateClientServerCommand extends AbstractCreateCommand {
@Parameter(names="-issuer", description = "The id of the issuer certificates in the trust store", required = true)
long issuer;
@Parameter(names = "-issuerPrivateKey", description = "The file storing the issuer certificate's private key.", required = true)
String issuerPrivateKey;
@Parameter(names = "-certificate", description = "The file in which to store the certificate", required = true)
String certificate;
}
@Parameters(commandNames = CREATE_CLIENT, commandDescription = "Creates a client certificate")
private static class CreateClientCommand extends AbstractCreateClientServerCommand {
}
@Parameters(commandNames = CREATE_SERVER, commandDescription = "Creates a server certificate")
private static class CreateServerCommand extends AbstractCreateClientServerCommand {
}
@Parameters(commandNames = CHAIN_CERTIFICATE, commandDescription = "Chains a new certificate to an existing certificate or certificate chain")
private static class ChainCertificateCommand extends AbstractCreateCommand {
@Parameter(names = "-existingPrivateKey", description = "The private key of the certificate being chained or the private key of the last certificate in the chain", required = true)
String existingPrivateKey;
@Parameter(names = "-existingCertificate", description = "The file name of the existing certificate or certificate chain", required = true)
String existingCertificate;
@Parameter(names = "-certificate", description = "The file in which to store the new certificate chain", required = true)
String certificate;
}
@Parameters(commandNames = LIST_AUTHORITIES, commandDescription = "List the certificates in the trust store.")
private static class ListAuthorities {
}
@Parameters(commandNames = SHOW_CERTIFICATE, commandDescription = "Displays the contents of a certificate.")
private static class ShowCertificateCommand {
@Parameter(names = "-certificate", description = "The certificate or certificate chain to display", required = true)
String certificate;
}
@Parameters(commandNames = IMPORT_CERTIFICATES, commandDescription = "Imports certificates into the trust store from another trust store or certificate.")
private static class ImportCertificatesCommand {
@Parameter(names = "-certificate", description = "The trust store, certificate, certificate chain to import", required = true)
String certificate;
}
@Parameters(commandNames = VALIDATE_CERTIFICATE, commandDescription = "Validates a certificate or certificate chain against the trust store")
private static class ValidateCommand {
@Parameter(names = "-certificate", description = "The certificate or certificate chain to validate", required = true)
String certificate;
}
@Parameters(commandNames = REMOVE_AUTHORITY, commandDescription = "Removes an authority certificate from the trust store")
private static class RemoveAuthorityCommand {
@Parameter(names = "-serialNumber", description = "The serial number of the certificate to be removed", required = true)
Long serialNumber;
}
}