Skip to content

Set keyUsage for generated HTTP certificates and self-signed CA (#126376) #126448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/126376.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 126376
summary: Set `keyUsage` for generated HTTP certificates and self-signed CA
area: TLS
type: bug
issues:
- 117769
6 changes: 5 additions & 1 deletion docs/reference/commands/certutil.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use with Transport Layer Security (TLS) in the {stack}.
--------------------------------------------------
bin/elasticsearch-certutil
(
(ca [--ca-dn <name>] [--days <n>] [--pem])
(ca [--ca-dn <name>] [--keyusage <key_usages>] [--days <n>] [--pem])

| (cert ([--ca <file_path>] | [--ca-cert <file_path> --ca-key <file_path>])
[--ca-dn <name>] [--ca-pass <password>] [--days <n>]
Expand Down Expand Up @@ -158,6 +158,10 @@ parameter is only applicable to the `cert` parameter.
`--ca-pass <password>`:: Specifies the password for an existing CA private key
or the generated CA private key. This parameter is only applicable to the `cert` parameter

`--keyusage <key_usages>`:: 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.

`--days <n>`:: 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@
import static org.elasticsearch.discovery.SettingsBasedSeedHostsProvider.DISCOVERY_SEED_HOSTS_SETTING;
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
import static org.elasticsearch.xpack.core.security.CommandLineHttpClient.createURL;
import static org.elasticsearch.xpack.security.cli.CertGenUtils.buildKeyUsage;
import static org.elasticsearch.xpack.security.cli.HttpCertificateCommand.DEFAULT_CA_KEY_USAGE;
import static org.elasticsearch.xpack.security.cli.HttpCertificateCommand.DEFAULT_CERT_KEY_USAGE;

/**
* Configures a new cluster node, by appending to the elasticsearch.yml, so that it forms a single node cluster with
Expand Down Expand Up @@ -411,7 +414,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
null,
true,
TRANSPORT_CA_CERTIFICATE_DAYS,
SIGNATURE_ALGORITHM
SIGNATURE_ALGORITHM,
null,
Set.of()
);
// transport key/certificate
final KeyPair transportKeyPair = CertGenUtils.generateKeyPair(TRANSPORT_KEY_SIZE);
Expand All @@ -424,7 +429,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
transportCaKey,
false,
TRANSPORT_CERTIFICATE_DAYS,
SIGNATURE_ALGORITHM
SIGNATURE_ALGORITHM,
null,
Set.of()
);

final KeyPair httpCaKeyPair = CertGenUtils.generateKeyPair(HTTP_CA_KEY_SIZE);
Expand All @@ -438,7 +445,9 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
null,
true,
HTTP_CA_CERTIFICATE_DAYS,
SIGNATURE_ALGORITHM
SIGNATURE_ALGORITHM,
buildKeyUsage(DEFAULT_CA_KEY_USAGE),
Set.of()
);
} catch (Throwable t) {
try {
Expand All @@ -464,6 +473,7 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
false,
HTTP_CERTIFICATE_DAYS,
SIGNATURE_ALGORITHM,
buildKeyUsage(DEFAULT_CERT_KEY_USAGE),
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
Expand Down Expand Up @@ -53,10 +54,14 @@
import java.sql.Date;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;

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

/**
* The mapping of key usage names to their corresponding integer values as defined in {@code KeyUsage} class.
*/
public static final Map<String, Integer> KEY_USAGE_MAPPINGS = Collections.unmodifiableMap(
new TreeMap<>(
Map.ofEntries(
Map.entry("digitalSignature", KeyUsage.digitalSignature),
Map.entry("nonRepudiation", KeyUsage.nonRepudiation),
Map.entry("keyEncipherment", KeyUsage.keyEncipherment),
Map.entry("dataEncipherment", KeyUsage.dataEncipherment),
Map.entry("keyAgreement", KeyUsage.keyAgreement),
Map.entry("keyCertSign", KeyUsage.keyCertSign),
Map.entry("cRLSign", KeyUsage.cRLSign),
Map.entry("encipherOnly", KeyUsage.encipherOnly),
Map.entry("decipherOnly", KeyUsage.decipherOnly)
)
)
);

private CertGenUtils() {}

/**
* Generates a CA certificate
*/
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days)
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days, KeyUsage keyUsage)
throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null);
return generateSignedCertificate(x500Principal, null, keyPair, null, null, true, days, null, keyUsage, Set.of());
}

/**
Expand All @@ -107,7 +131,7 @@ public static X509Certificate generateSignedCertificate(
PrivateKey caPrivKey,
int days
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null);
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null, null, Set.of());
}

/**
Expand All @@ -123,54 +147,14 @@ public static X509Certificate generateSignedCertificate(
* certificate
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
* certificate
* @param days no of days certificate will be valid from now
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
* @return a signed {@link X509Certificate}
*/
public static X509Certificate generateSignedCertificate(
X500Principal principal,
GeneralNames subjectAltNames,
KeyPair keyPair,
X509Certificate caCert,
PrivateKey caPrivKey,
int days,
String signatureAlgorithm
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, signatureAlgorithm);
}

/**
* Generates a signed certificate
*
* @param principal the principal of the certificate; commonly referred to as the
* distinguished name (DN)
* @param subjectAltNames the subject alternative names that should be added to the
* certificate as an X509v3 extension. May be {@code null}
* @param keyPair the key pair that will be associated with the certificate
* @param caCert the CA certificate. If {@code null}, this results in a self signed
* certificate
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
* certificate
* @param isCa whether or not the generated certificate is a CA
* @param days no of days certificate will be valid from now
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
* @param keyUsage the key usage that should be added to the certificate as a X509v3 extension (can be {@code null})
* @param extendedKeyUsages the extended key usages that should be added to the certificate as a X509v3 extension (can be empty)
* @return a signed {@link X509Certificate}
*/
public static X509Certificate generateSignedCertificate(
X500Principal principal,
GeneralNames subjectAltNames,
KeyPair keyPair,
X509Certificate caCert,
PrivateKey caPrivKey,
boolean isCa,
int days,
String signatureAlgorithm
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, isCa, days, signatureAlgorithm, Set.of());
}

public static X509Certificate generateSignedCertificate(
X500Principal principal,
GeneralNames subjectAltNames,
Expand All @@ -180,6 +164,7 @@ public static X509Certificate generateSignedCertificate(
boolean isCa,
int days,
String signatureAlgorithm,
KeyUsage keyUsage,
Set<ExtendedKeyUsage> extendedKeyUsages
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
Expand All @@ -198,6 +183,7 @@ public static X509Certificate generateSignedCertificate(
notBefore,
notAfter,
signatureAlgorithm,
keyUsage,
extendedKeyUsages
);
}
Expand All @@ -223,6 +209,7 @@ public static X509Certificate generateSignedCertificate(
notBefore,
notAfter,
signatureAlgorithm,
null,
Set.of()
);
}
Expand All @@ -237,6 +224,7 @@ public static X509Certificate generateSignedCertificate(
ZonedDateTime notBefore,
ZonedDateTime notAfter,
String signatureAlgorithm,
KeyUsage keyUsage,
Set<ExtendedKeyUsage> extendedKeyUsages
) throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
final BigInteger serial = CertGenUtils.getSerial();
Expand Down Expand Up @@ -272,6 +260,11 @@ public static X509Certificate generateSignedCertificate(
}
builder.addExtension(Extension.basicConstraints, isCa, new BasicConstraints(isCa));

if (keyUsage != null) {
// as per RFC 5280 (section 4.2.1.3), if the key usage is present, then it SHOULD be marked as critical.
final boolean isCritical = true;
builder.addExtension(Extension.keyUsage, isCritical, keyUsage);
}
if (extendedKeyUsages != null) {
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
builder.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
Expand Down Expand Up @@ -318,7 +311,7 @@ private static String getDefaultSignatureAlgorithm(PrivateKey key) {
*/
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws IOException,
OperatorCreationException {
return generateCSR(keyPair, principal, sanList, Set.of());
return generateCSR(keyPair, principal, sanList, null, Set.of());
}

/**
Expand All @@ -335,6 +328,7 @@ static PKCS10CertificationRequest generateCSR(
KeyPair keyPair,
X500Principal principal,
GeneralNames sanList,
KeyUsage keyUsage,
Set<ExtendedKeyUsage> extendedKeyUsages
) throws IOException, OperatorCreationException {
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
Expand All @@ -347,7 +341,9 @@ static PKCS10CertificationRequest generateCSR(
if (sanList != null) {
extGen.addExtension(Extension.subjectAlternativeName, false, sanList);
}

if (keyUsage != null) {
extGen.addExtension(Extension.keyUsage, true, keyUsage);
}
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
extGen.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
}
Expand Down Expand Up @@ -430,4 +426,31 @@ public static GeneralName createCommonName(String cn) {
public static String buildDnFromDomain(String domain) {
return "DC=" + domain.replace(".", ",DC=");
}

public static KeyUsage buildKeyUsage(Collection<String> keyUsages) {
if (keyUsages == null || keyUsages.isEmpty()) {
return null;
}

int usageBits = 0;
for (String keyUsageName : keyUsages) {
Integer keyUsageValue = findKeyUsageByName(keyUsageName);
if (keyUsageValue == null) {
throw new IllegalArgumentException("Unknown keyUsage: " + keyUsageName);
}
usageBits |= keyUsageValue;
}
return new KeyUsage(usageBits);
}

public static boolean isValidKeyUsage(String keyUsage) {
return findKeyUsageByName(keyUsage) != null;
}

private static Integer findKeyUsageByName(String keyUsageName) {
if (keyUsageName == null) {
return null;
}
return KEY_USAGE_MAPPINGS.get(keyUsageName.trim());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ static CAInfo getCAInfo(
// generate the CA keys and cert
X500Principal x500Principal = new X500Principal(dn);
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days);
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days, null);
final char[] password;
if (prompt) {
password = terminal.readSecret("Enter password for CA private key: ");
Expand Down
Loading