Skip to content

Commit 2bb7b53

Browse files
authored
Add certutil http command (#50952)
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. Backport of: #49827
1 parent 22ba759 commit 2bb7b53

File tree

19 files changed

+2494
-5
lines changed

19 files changed

+2494
-5
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,20 @@ public static List<Certificate> readCertificates(InputStream input) throws Certi
130130
* return the password for that key. If it returns {@code null}, then the key-pair for that alias is not read.
131131
*/
132132
public static Map<Certificate, Key> readPkcs12KeyPairs(Path path, char[] password, Function<String, char[]> keyPassword)
133-
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
134-
final KeyStore store = readKeyStore(path, "PKCS12", password);
133+
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
134+
return readKeyPairsFromKeystore(path, "PKCS12", password, keyPassword);
135+
}
136+
137+
public static Map<Certificate, Key> readKeyPairsFromKeystore(Path path, String storeType, char[] password,
138+
Function<String, char[]> keyPassword)
139+
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
140+
141+
final KeyStore store = readKeyStore(path, storeType, password);
142+
return readKeyPairsFromKeystore(store, keyPassword);
143+
}
144+
145+
static Map<Certificate, Key> readKeyPairsFromKeystore(KeyStore store, Function<String, char[]> keyPassword)
146+
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
135147
final Enumeration<String> enumeration = store.aliases();
136148
final Map<Certificate, Key> map = new HashMap<>(store.size());
137149
while (enumeration.hasMoreElements()) {
Lines changed: 90 additions & 0 deletions
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>("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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 5 additions & 0 deletions
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
rootProject.globalInfo.ready {
2328
if (BuildParams.inFipsJvm) {
2429
test.enabled = false

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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 4 additions & 3 deletions
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)