Skip to content

Commit 112859b

Browse files
Set keyUsage for generated HTTP certificates and self-signed CA (#126376) (#126447)
The `elasticsearch-certutil http` command, and security auto-configuration, generate the HTTP certificate and CA without setting the `keyUsage` extension. This PR fixes this by setting (by default): - `keyCertSign` and `cRLSign` for self-signed CAs - `digitalSignature` and `keyEncipherment` for HTTP certificates and CSRs These defaults can be overridden when running `elasticsearch-certutil http` command. The user will be prompted to change them as they wish. For `elasticsearch-certutil ca`, the default value can be overridden by passing the `--keysage` option, e.g. ``` elasticsearch-certutil ca --keyusage "digitalSignature,keyCertSign,cRLSign" -pem ``` Fixes #117769
1 parent 67d688e commit 112859b

File tree

12 files changed

+404
-72
lines changed

12 files changed

+404
-72
lines changed

Diff for: docs/changelog/126376.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 126376
2+
summary: Set `keyUsage` for generated HTTP certificates and self-signed CA
3+
area: TLS
4+
type: bug
5+
issues:
6+
- 117769

Diff for: docs/reference/elasticsearch/command-line-tools/certutil.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The `elasticsearch-certutil` command simplifies the creation of certificates for
1313
```shell
1414
bin/elasticsearch-certutil
1515
(
16-
(ca [--ca-dn <name>] [--days <n>] [--pem])
16+
(ca [--ca-dn <name>] [--keyusage <key_usages>] [--days <n>] [--pem])
1717

1818
| (cert ([--ca <file_path>] | [--ca-cert <file_path> --ca-key <file_path>])
1919
[--ca-dn <name>] [--ca-pass <password>] [--days <n>]
@@ -105,6 +105,9 @@ The `http` mode guides you through the process of generating certificates for us
105105
`--ca-pass <password>`
106106
: Specifies the password for an existing CA private key or the generated CA private key. This parameter is only applicable to the `cert` parameter
107107

108+
`--keyusage <key_usages>`
109+
: Specifies a comma-separated list of key usage restrictions (as per RFC 5280) that are used for the generated CA certificate. The default value is `keyCertSign,cRLSign`. This parameter may only be used with the `ca` parameter.
110+
108111
`--days <n>`
109112
: Specifies an integer value that represents the number of days the generated certificates are valid. The default value is `1095`. This parameter cannot be used with the `csr` or `http` parameters.
110113

Diff for: x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/AutoConfigureNode.java

+13-3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@
102102
import static org.elasticsearch.discovery.SettingsBasedSeedHostsProvider.DISCOVERY_SEED_HOSTS_SETTING;
103103
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
104104
import static org.elasticsearch.xpack.core.security.CommandLineHttpClient.createURL;
105+
import static org.elasticsearch.xpack.security.cli.CertGenUtils.buildKeyUsage;
106+
import static org.elasticsearch.xpack.security.cli.HttpCertificateCommand.DEFAULT_CA_KEY_USAGE;
107+
import static org.elasticsearch.xpack.security.cli.HttpCertificateCommand.DEFAULT_CERT_KEY_USAGE;
105108

106109
/**
107110
* Configures a new cluster node, by appending to the elasticsearch.yml, so that it forms a single node cluster with
@@ -411,7 +414,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
411414
null,
412415
true,
413416
TRANSPORT_CA_CERTIFICATE_DAYS,
414-
SIGNATURE_ALGORITHM
417+
SIGNATURE_ALGORITHM,
418+
null,
419+
Set.of()
415420
);
416421
// transport key/certificate
417422
final KeyPair transportKeyPair = CertGenUtils.generateKeyPair(TRANSPORT_KEY_SIZE);
@@ -424,7 +429,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
424429
transportCaKey,
425430
false,
426431
TRANSPORT_CERTIFICATE_DAYS,
427-
SIGNATURE_ALGORITHM
432+
SIGNATURE_ALGORITHM,
433+
null,
434+
Set.of()
428435
);
429436

430437
final KeyPair httpCaKeyPair = CertGenUtils.generateKeyPair(HTTP_CA_KEY_SIZE);
@@ -438,7 +445,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
438445
null,
439446
true,
440447
HTTP_CA_CERTIFICATE_DAYS,
441-
SIGNATURE_ALGORITHM
448+
SIGNATURE_ALGORITHM,
449+
buildKeyUsage(DEFAULT_CA_KEY_USAGE),
450+
Set.of()
442451
);
443452
} catch (Throwable t) {
444453
try {
@@ -464,6 +473,7 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
464473
false,
465474
HTTP_CERTIFICATE_DAYS,
466475
SIGNATURE_ALGORITHM,
476+
buildKeyUsage(DEFAULT_CERT_KEY_USAGE),
467477
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
468478
);
469479

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

+70-47
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
2121
import org.bouncycastle.asn1.x509.GeneralName;
2222
import org.bouncycastle.asn1.x509.GeneralNames;
23+
import org.bouncycastle.asn1.x509.KeyUsage;
2324
import org.bouncycastle.asn1.x509.Time;
2425
import org.bouncycastle.cert.CertIOException;
2526
import org.bouncycastle.cert.X509CertificateHolder;
@@ -53,10 +54,14 @@
5354
import java.sql.Date;
5455
import java.time.ZoneOffset;
5556
import java.time.ZonedDateTime;
57+
import java.util.Collection;
58+
import java.util.Collections;
5659
import java.util.HashSet;
5760
import java.util.Locale;
61+
import java.util.Map;
5862
import java.util.Objects;
5963
import java.util.Set;
64+
import java.util.TreeMap;
6065

6166
import javax.net.ssl.X509ExtendedKeyManager;
6267
import javax.net.ssl.X509ExtendedTrustManager;
@@ -73,14 +78,33 @@ public class CertGenUtils {
7378
private static final int SERIAL_BIT_LENGTH = 20 * 8;
7479
private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
7580

81+
/**
82+
* The mapping of key usage names to their corresponding integer values as defined in {@code KeyUsage} class.
83+
*/
84+
public static final Map<String, Integer> KEY_USAGE_MAPPINGS = Collections.unmodifiableMap(
85+
new TreeMap<>(
86+
Map.ofEntries(
87+
Map.entry("digitalSignature", KeyUsage.digitalSignature),
88+
Map.entry("nonRepudiation", KeyUsage.nonRepudiation),
89+
Map.entry("keyEncipherment", KeyUsage.keyEncipherment),
90+
Map.entry("dataEncipherment", KeyUsage.dataEncipherment),
91+
Map.entry("keyAgreement", KeyUsage.keyAgreement),
92+
Map.entry("keyCertSign", KeyUsage.keyCertSign),
93+
Map.entry("cRLSign", KeyUsage.cRLSign),
94+
Map.entry("encipherOnly", KeyUsage.encipherOnly),
95+
Map.entry("decipherOnly", KeyUsage.decipherOnly)
96+
)
97+
)
98+
);
99+
76100
private CertGenUtils() {}
77101

78102
/**
79103
* Generates a CA certificate
80104
*/
81-
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days)
105+
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days, KeyUsage keyUsage)
82106
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
83-
return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null);
107+
return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null, keyUsage, Set.of());
84108
}
85109

86110
/**
@@ -107,7 +131,7 @@ public static X509Certificate generateSignedCertificate(
107131
PrivateKey caPrivKey,
108132
int days
109133
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
110-
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null);
134+
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null, null, Set.of());
111135
}
112136

113137
/**
@@ -123,54 +147,14 @@ public static X509Certificate generateSignedCertificate(
123147
* certificate
124148
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
125149
* certificate
126-
* @param days no of days certificate will be valid from now
127-
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
128-
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
129-
* @return a signed {@link X509Certificate}
130-
*/
131-
public static X509Certificate generateSignedCertificate(
132-
X500Principal principal,
133-
GeneralNames subjectAltNames,
134-
KeyPair keyPair,
135-
X509Certificate caCert,
136-
PrivateKey caPrivKey,
137-
int days,
138-
String signatureAlgorithm
139-
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
140-
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, signatureAlgorithm);
141-
}
142-
143-
/**
144-
* Generates a signed certificate
145-
*
146-
* @param principal the principal of the certificate; commonly referred to as the
147-
* distinguished name (DN)
148-
* @param subjectAltNames the subject alternative names that should be added to the
149-
* certificate as an X509v3 extension. May be {@code null}
150-
* @param keyPair the key pair that will be associated with the certificate
151-
* @param caCert the CA certificate. If {@code null}, this results in a self signed
152-
* certificate
153-
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
154-
* certificate
155150
* @param isCa whether or not the generated certificate is a CA
156151
* @param days no of days certificate will be valid from now
157152
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
158153
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
154+
* @param keyUsage the key usage that should be added to the certificate as a X509v3 extension (can be {@code null})
155+
* @param extendedKeyUsages the extended key usages that should be added to the certificate as a X509v3 extension (can be empty)
159156
* @return a signed {@link X509Certificate}
160157
*/
161-
public static X509Certificate generateSignedCertificate(
162-
X500Principal principal,
163-
GeneralNames subjectAltNames,
164-
KeyPair keyPair,
165-
X509Certificate caCert,
166-
PrivateKey caPrivKey,
167-
boolean isCa,
168-
int days,
169-
String signatureAlgorithm
170-
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
171-
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, isCa, days, signatureAlgorithm, Set.of());
172-
}
173-
174158
public static X509Certificate generateSignedCertificate(
175159
X500Principal principal,
176160
GeneralNames subjectAltNames,
@@ -180,6 +164,7 @@ public static X509Certificate generateSignedCertificate(
180164
boolean isCa,
181165
int days,
182166
String signatureAlgorithm,
167+
KeyUsage keyUsage,
183168
Set<ExtendedKeyUsage> extendedKeyUsages
184169
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
185170
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
@@ -198,6 +183,7 @@ public static X509Certificate generateSignedCertificate(
198183
notBefore,
199184
notAfter,
200185
signatureAlgorithm,
186+
keyUsage,
201187
extendedKeyUsages
202188
);
203189
}
@@ -223,6 +209,7 @@ public static X509Certificate generateSignedCertificate(
223209
notBefore,
224210
notAfter,
225211
signatureAlgorithm,
212+
null,
226213
Set.of()
227214
);
228215
}
@@ -237,6 +224,7 @@ public static X509Certificate generateSignedCertificate(
237224
ZonedDateTime notBefore,
238225
ZonedDateTime notAfter,
239226
String signatureAlgorithm,
227+
KeyUsage keyUsage,
240228
Set<ExtendedKeyUsage> extendedKeyUsages
241229
) throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
242230
final BigInteger serial = CertGenUtils.getSerial();
@@ -272,6 +260,11 @@ public static X509Certificate generateSignedCertificate(
272260
}
273261
builder.addExtension(Extension.basicConstraints, isCa, new BasicConstraints(isCa));
274262

263+
if (keyUsage != null) {
264+
// as per RFC 5280 (section 4.2.1.3), if the key usage is present, then it SHOULD be marked as critical.
265+
final boolean isCritical = true;
266+
builder.addExtension(Extension.keyUsage, isCritical, keyUsage);
267+
}
275268
if (extendedKeyUsages != null) {
276269
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
277270
builder.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
@@ -318,7 +311,7 @@ private static String getDefaultSignatureAlgorithm(PrivateKey key) {
318311
*/
319312
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws IOException,
320313
OperatorCreationException {
321-
return generateCSR(keyPair, principal, sanList, Set.of());
314+
return generateCSR(keyPair, principal, sanList, null, Set.of());
322315
}
323316

324317
/**
@@ -335,6 +328,7 @@ static PKCS10CertificationRequest generateCSR(
335328
KeyPair keyPair,
336329
X500Principal principal,
337330
GeneralNames sanList,
331+
KeyUsage keyUsage,
338332
Set<ExtendedKeyUsage> extendedKeyUsages
339333
) throws IOException, OperatorCreationException {
340334
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
@@ -347,7 +341,9 @@ static PKCS10CertificationRequest generateCSR(
347341
if (sanList != null) {
348342
extGen.addExtension(Extension.subjectAlternativeName, false, sanList);
349343
}
350-
344+
if (keyUsage != null) {
345+
extGen.addExtension(Extension.keyUsage, true, keyUsage);
346+
}
351347
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
352348
extGen.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
353349
}
@@ -430,4 +426,31 @@ public static GeneralName createCommonName(String cn) {
430426
public static String buildDnFromDomain(String domain) {
431427
return "DC=" + domain.replace(".", ",DC=");
432428
}
429+
430+
public static KeyUsage buildKeyUsage(Collection<String> keyUsages) {
431+
if (keyUsages == null || keyUsages.isEmpty()) {
432+
return null;
433+
}
434+
435+
int usageBits = 0;
436+
for (String keyUsageName : keyUsages) {
437+
Integer keyUsageValue = findKeyUsageByName(keyUsageName);
438+
if (keyUsageValue == null) {
439+
throw new IllegalArgumentException("Unknown keyUsage: " + keyUsageName);
440+
}
441+
usageBits |= keyUsageValue;
442+
}
443+
return new KeyUsage(usageBits);
444+
}
445+
446+
public static boolean isValidKeyUsage(String keyUsage) {
447+
return findKeyUsageByName(keyUsage) != null;
448+
}
449+
450+
private static Integer findKeyUsageByName(String keyUsageName) {
451+
if (keyUsageName == null) {
452+
return null;
453+
}
454+
return KEY_USAGE_MAPPINGS.get(keyUsageName.trim());
455+
}
433456
}

Diff for: x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/CertificateGenerateTool.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ static CAInfo getCAInfo(
403403
// generate the CA keys and cert
404404
X500Principal x500Principal = new X500Principal(dn);
405405
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
406-
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days);
406+
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days, null);
407407
final char[] password;
408408
if (prompt) {
409409
password = terminal.readSecret("Enter password for CA private key: ");

0 commit comments

Comments
 (0)