Skip to content

Commit

Permalink
Add certutil http command (#49827)
Browse files Browse the repository at this point in the history
This adds a new "http" sub-command to the certutil CLI tool.

The http command generates certificates/CSRs for use on the http
interface of an elasticsearch node/cluster.
It is designed to be a guided tool that provides explanations and
sugestions for each of the configuration options. The generated zip
file output includes extensive "readme" documentation and sample
configuration files for core Elastic products.
  • Loading branch information
tvernum authored Jan 13, 2020
1 parent c30e05e commit 39a5d75
Show file tree
Hide file tree
Showing 19 changed files with 2,477 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,20 @@ public static List<Certificate> readCertificates(InputStream input) throws Certi
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
*/
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
final KeyStore store = readKeyStore(path, "PKCS12", password);
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
return readKeyPairsFromKeystore(path, "PKCS12", password, keyPassword);
}

public static Map<Certificate, Key> readKeyPairsFromKeystore(Path path, String storeType, char[] password,
Function<String, char[]> keyPassword)
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {

final KeyStore store = readKeyStore(path, storeType, password);
return readKeyPairsFromKeystore(store, keyPassword);
}

static Map<Certificate, Key> readKeyPairsFromKeystore(KeyStore store, Function<String, char[]> keyPassword)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
final Enumeration<String> enumeration = store.aliases();
final Map<Certificate, Key> map = new HashMap<>(store.size());
while (enumeration.hasMoreElements()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.test;

import org.hamcrest.CustomMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;

public class FileMatchers {
public static Matcher<Path> pathExists(LinkOption... options) {
return new CustomMatcher<>("Path exists") {
@Override
public boolean matches(Object item) {
if (item instanceof Path) {
Path path = (Path) item;
return Files.exists(path, options);
} else {
return false;
}

}
};
}

public static Matcher<Path> isDirectory(LinkOption... options) {
return new FileTypeMatcher("directory", options) {
@Override
protected boolean matchPath(Path path) {
return Files.isDirectory(path, options);
}
};
}

public static Matcher<Path> isRegularFile(LinkOption... options) {
return new FileTypeMatcher("regular file", options) {
@Override
protected boolean matchPath(Path path) {
return Files.isRegularFile(path, options);
}
};
}

private abstract static class FileTypeMatcher extends CustomMatcher<Path> {
private final LinkOption[] options;

FileTypeMatcher(String typeName, LinkOption... options) {
super("Path is " + typeName);
this.options = options;
}

@Override
public boolean matches(Object item) {
if (item instanceof Path) {
Path path = (Path) item;
return matchPath(path);
} else {
return false;
}
}

protected abstract boolean matchPath(Path path);

@Override
public void describeMismatch(Object item, Description description) {
super.describeMismatch(item, description);
if (item instanceof Path) {
Path path = (Path) item;
if (Files.exists(path, options) == false) {
description.appendText(" (file not found)");
} else if (Files.isDirectory(path, options)) {
description.appendText(" (directory)");
} else if (Files.isSymbolicLink(path)) {
description.appendText(" (symlink)");
} else if (Files.isRegularFile(path, options)) {
description.appendText(" (regular file)");
} else {
description.appendText(" (unknown file type)");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

public class TestMatchers extends Matchers {

/**
* @deprecated Use {@link FileMatchers#pathExists}
*/
@Deprecated
public static Matcher<Path> pathExists(Path path, LinkOption... options) {
return new CustomMatcher<Path>("Path " + path + " exists") {
@Override
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugin/security/cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ dependencyLicenses {
mapping from: /bc.*/, to: 'bouncycastle'
}

forbiddenPatterns {
exclude '**/*.p12'
exclude '**/*.jks'
}

if (BuildParams.inFipsJvm) {
test.enabled = false
testingConventions.enabled = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ private static X509Certificate generateSignedCertificate(X500Principal principal
throw new IllegalArgumentException("the certificate must be valid for at least one day");
}
final ZonedDateTime notAfter = notBefore.plusDays(days);
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, isCa, notBefore, notAfter,
signatureAlgorithm);
}

public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
X509Certificate caCert, PrivateKey caPrivKey, boolean isCa,
ZonedDateTime notBefore, ZonedDateTime notAfter, String signatureAlgorithm)
throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
final BigInteger serial = CertGenUtils.getSerial();
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public static void main(String[] args) throws Exception {
subcommands.put("csr", new SigningRequestCommand());
subcommands.put("cert", new GenerateCertificateCommand());
subcommands.put("ca", new CertificateAuthorityCommand());
subcommands.put("http", new HttpCertificateCommand());
}


Expand Down Expand Up @@ -920,7 +921,7 @@ static Collection<CertificateInformation> parseFile(Path file) throws Exception
}
}

private static PEMEncryptor getEncrypter(char[] password) {
static PEMEncryptor getEncrypter(char[] password) {
return new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(BC_PROV).build(password);
}

Expand Down Expand Up @@ -1036,7 +1037,7 @@ private static PrivateKey readPrivateKey(Path path, char[] password, Terminal te
}
}

private static GeneralNames getSubjectAlternativeNamesValue(List<String> ipAddresses, List<String> dnsNames, List<String> commonNames) {
static GeneralNames getSubjectAlternativeNamesValue(List<String> ipAddresses, List<String> dnsNames, List<String> commonNames) {
Set<GeneralName> generalNameList = new HashSet<>();
for (String ip : ipAddresses) {
generalNameList.add(new GeneralName(GeneralName.iPAddress, ip));
Expand All @@ -1056,7 +1057,7 @@ private static GeneralNames getSubjectAlternativeNamesValue(List<String> ipAddre
return new GeneralNames(generalNameList.toArray(new GeneralName[0]));
}

private static boolean isAscii(char[] str) {
static boolean isAscii(char[] str) {
return ASCII_ENCODER.canEncode(CharBuffer.wrap(str));
}

Expand Down
Loading

0 comments on commit 39a5d75

Please sign in to comment.