Skip to content

Commit 39a5d75

Browse files
authored
Add certutil http command (#49827)
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.
1 parent c30e05e commit 39a5d75

File tree

19 files changed

+2477
-5
lines changed

19 files changed

+2477
-5
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/CertParsingUtils.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,20 @@ public static List<Certificate> readCertificates(InputStream input) throws Certi
120120
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
121121
*/
122122
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword)
123-
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
124-
final KeyStore store = readKeyStore(path, "PKCS12", password);
123+
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
124+
return readKeyPairsFromKeystore(path, "PKCS12", password, keyPassword);
125+
}
126+
127+
public static Map<Certificate, Key> readKeyPairsFromKeystore(Path path, String storeType, char[] password,
128+
Function<String, char[]> keyPassword)
129+
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
130+
131+
final KeyStore store = readKeyStore(path, storeType, password);
132+
return readKeyPairsFromKeystore(store, keyPassword);
133+
}
134+
135+
static Map<Certificate, Key> readKeyPairsFromKeystore(KeyStore store, Function<String, char[]> keyPassword)
136+
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
125137
final Enumeration<String> enumeration = store.aliases();
126138
final Map<Certificate, Key> map = new HashMap<>(store.size());
127139
while (enumeration.hasMoreElements()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.test;
8+
9+
import org.hamcrest.CustomMatcher;
10+
import org.hamcrest.Description;
11+
import org.hamcrest.Matcher;
12+
13+
import java.nio.file.Files;
14+
import java.nio.file.LinkOption;
15+
import java.nio.file.Path;
16+
17+
public class FileMatchers {
18+
public static Matcher<Path> pathExists(LinkOption... options) {
19+
return new CustomMatcher<>("Path exists") {
20+
@Override
21+
public boolean matches(Object item) {
22+
if (item instanceof Path) {
23+
Path path = (Path) item;
24+
return Files.exists(path, options);
25+
} else {
26+
return false;
27+
}
28+
29+
}
30+
};
31+
}
32+
33+
public static Matcher<Path> isDirectory(LinkOption... options) {
34+
return new FileTypeMatcher("directory", options) {
35+
@Override
36+
protected boolean matchPath(Path path) {
37+
return Files.isDirectory(path, options);
38+
}
39+
};
40+
}
41+
42+
public static Matcher<Path> isRegularFile(LinkOption... options) {
43+
return new FileTypeMatcher("regular file", options) {
44+
@Override
45+
protected boolean matchPath(Path path) {
46+
return Files.isRegularFile(path, options);
47+
}
48+
};
49+
}
50+
51+
private abstract static class FileTypeMatcher extends CustomMatcher<Path> {
52+
private final LinkOption[] options;
53+
54+
FileTypeMatcher(String typeName, LinkOption... options) {
55+
super("Path is " + typeName);
56+
this.options = options;
57+
}
58+
59+
@Override
60+
public boolean matches(Object item) {
61+
if (item instanceof Path) {
62+
Path path = (Path) item;
63+
return matchPath(path);
64+
} else {
65+
return false;
66+
}
67+
}
68+
69+
protected abstract boolean matchPath(Path path);
70+
71+
@Override
72+
public void describeMismatch(Object item, Description description) {
73+
super.describeMismatch(item, description);
74+
if (item instanceof Path) {
75+
Path path = (Path) item;
76+
if (Files.exists(path, options) == false) {
77+
description.appendText(" (file not found)");
78+
} else if (Files.isDirectory(path, options)) {
79+
description.appendText(" (directory)");
80+
} else if (Files.isSymbolicLink(path)) {
81+
description.appendText(" (symlink)");
82+
} else if (Files.isRegularFile(path, options)) {
83+
description.appendText(" (regular file)");
84+
} else {
85+
description.appendText(" (unknown file type)");
86+
}
87+
}
88+
}
89+
}
90+
}

x-pack/plugin/core/src/test/java/org/elasticsearch/test/TestMatchers.java

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
public class TestMatchers extends Matchers {
2222

23+
/**
24+
* @deprecated Use {@link FileMatchers#pathExists}
25+
*/
26+
@Deprecated
2327
public static Matcher<Path> pathExists(Path path, LinkOption... options) {
2428
return new CustomMatcher<Path>("Path " + path + " exists") {
2529
@Override

x-pack/plugin/security/cli/build.gradle

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ dependencyLicenses {
1919
mapping from: /bc.*/, to: 'bouncycastle'
2020
}
2121

22+
forbiddenPatterns {
23+
exclude '**/*.p12'
24+
exclude '**/*.jks'
25+
}
26+
2227
if (BuildParams.inFipsJvm) {
2328
test.enabled = false
2429
testingConventions.enabled = false

x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertGenUtils.java

+8
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ private static X509Certificate generateSignedCertificate(X500Principal principal
157157
throw new IllegalArgumentException("the certificate must be valid for at least one day");
158158
}
159159
final ZonedDateTime notAfter = notBefore.plusDays(days);
160+
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, isCa, notBefore, notAfter,
161+
signatureAlgorithm);
162+
}
163+
164+
public static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair,
165+
X509Certificate caCert, PrivateKey caPrivKey, boolean isCa,
166+
ZonedDateTime notBefore, ZonedDateTime notAfter, String signatureAlgorithm)
167+
throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
160168
final BigInteger serial = CertGenUtils.getSerial();
161169
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
162170

x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateTool.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public static void main(String[] args) throws Exception {
142142
subcommands.put("csr", new SigningRequestCommand());
143143
subcommands.put("cert", new GenerateCertificateCommand());
144144
subcommands.put("ca", new CertificateAuthorityCommand());
145+
subcommands.put("http", new HttpCertificateCommand());
145146
}
146147

147148

@@ -920,7 +921,7 @@ static Collection<CertificateInformation> parseFile(Path file) throws Exception
920921
}
921922
}
922923

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

@@ -1036,7 +1037,7 @@ private static PrivateKey readPrivateKey(Path path, char[] password, Terminal te
10361037
}
10371038
}
10381039

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

1059-
private static boolean isAscii(char[] str) {
1060+
static boolean isAscii(char[] str) {
10601061
return ASCII_ENCODER.canEncode(CharBuffer.wrap(str));
10611062
}
10621063

0 commit comments

Comments
 (0)