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
This commit is contained in:
parent
67d688eadf
commit
112859b85d
|
@ -0,0 +1,6 @@
|
||||||
|
pr: 126376
|
||||||
|
summary: Set `keyUsage` for generated HTTP certificates and self-signed CA
|
||||||
|
area: TLS
|
||||||
|
type: bug
|
||||||
|
issues:
|
||||||
|
- 117769
|
|
@ -13,7 +13,7 @@ The `elasticsearch-certutil` command simplifies the creation of certificates for
|
||||||
```shell
|
```shell
|
||||||
bin/elasticsearch-certutil
|
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>])
|
| (cert ([--ca <file_path>] | [--ca-cert <file_path> --ca-key <file_path>])
|
||||||
[--ca-dn <name>] [--ca-pass <password>] [--days <n>]
|
[--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
|
||||||
`--ca-pass <password>`
|
`--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
|
: 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>`
|
`--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.
|
: 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.
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,9 @@ import static org.elasticsearch.common.ssl.PemUtils.parsePKCS8PemString;
|
||||||
import static org.elasticsearch.discovery.SettingsBasedSeedHostsProvider.DISCOVERY_SEED_HOSTS_SETTING;
|
import static org.elasticsearch.discovery.SettingsBasedSeedHostsProvider.DISCOVERY_SEED_HOSTS_SETTING;
|
||||||
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
|
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
|
||||||
import static org.elasticsearch.xpack.core.security.CommandLineHttpClient.createURL;
|
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
|
* 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 class AutoConfigureNode extends EnvironmentAwareCommand {
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
TRANSPORT_CA_CERTIFICATE_DAYS,
|
TRANSPORT_CA_CERTIFICATE_DAYS,
|
||||||
SIGNATURE_ALGORITHM
|
SIGNATURE_ALGORITHM,
|
||||||
|
null,
|
||||||
|
Set.of()
|
||||||
);
|
);
|
||||||
// transport key/certificate
|
// transport key/certificate
|
||||||
final KeyPair transportKeyPair = CertGenUtils.generateKeyPair(TRANSPORT_KEY_SIZE);
|
final KeyPair transportKeyPair = CertGenUtils.generateKeyPair(TRANSPORT_KEY_SIZE);
|
||||||
|
@ -424,7 +429,9 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
||||||
transportCaKey,
|
transportCaKey,
|
||||||
false,
|
false,
|
||||||
TRANSPORT_CERTIFICATE_DAYS,
|
TRANSPORT_CERTIFICATE_DAYS,
|
||||||
SIGNATURE_ALGORITHM
|
SIGNATURE_ALGORITHM,
|
||||||
|
null,
|
||||||
|
Set.of()
|
||||||
);
|
);
|
||||||
|
|
||||||
final KeyPair httpCaKeyPair = CertGenUtils.generateKeyPair(HTTP_CA_KEY_SIZE);
|
final KeyPair httpCaKeyPair = CertGenUtils.generateKeyPair(HTTP_CA_KEY_SIZE);
|
||||||
|
@ -438,7 +445,9 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
HTTP_CA_CERTIFICATE_DAYS,
|
HTTP_CA_CERTIFICATE_DAYS,
|
||||||
SIGNATURE_ALGORITHM
|
SIGNATURE_ALGORITHM,
|
||||||
|
buildKeyUsage(DEFAULT_CA_KEY_USAGE),
|
||||||
|
Set.of()
|
||||||
);
|
);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
try {
|
try {
|
||||||
|
@ -464,6 +473,7 @@ public class AutoConfigureNode extends EnvironmentAwareCommand {
|
||||||
false,
|
false,
|
||||||
HTTP_CERTIFICATE_DAYS,
|
HTTP_CERTIFICATE_DAYS,
|
||||||
SIGNATURE_ALGORITHM,
|
SIGNATURE_ALGORITHM,
|
||||||
|
buildKeyUsage(DEFAULT_CERT_KEY_USAGE),
|
||||||
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
|
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.bouncycastle.asn1.x509.Extension;
|
||||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
import org.bouncycastle.asn1.x509.Time;
|
import org.bouncycastle.asn1.x509.Time;
|
||||||
import org.bouncycastle.cert.CertIOException;
|
import org.bouncycastle.cert.CertIOException;
|
||||||
import org.bouncycastle.cert.X509CertificateHolder;
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
@ -53,10 +54,14 @@ import java.security.cert.X509Certificate;
|
||||||
import java.sql.Date;
|
import java.sql.Date;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
@ -73,14 +78,33 @@ public class CertGenUtils {
|
||||||
private static final int SERIAL_BIT_LENGTH = 20 * 8;
|
private static final int SERIAL_BIT_LENGTH = 20 * 8;
|
||||||
private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider();
|
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() {}
|
private CertGenUtils() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a CA certificate
|
* 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 {
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,7 +131,7 @@ public class CertGenUtils {
|
||||||
PrivateKey caPrivKey,
|
PrivateKey caPrivKey,
|
||||||
int days
|
int days
|
||||||
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException {
|
) 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,54 +147,14 @@ public class CertGenUtils {
|
||||||
* certificate
|
* certificate
|
||||||
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
|
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed
|
||||||
* certificate
|
* 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 isCa whether or not the generated certificate is a CA
|
||||||
* @param days no of days certificate will be valid from now
|
* @param days no of days certificate will be valid from now
|
||||||
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
|
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
|
||||||
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
|
* 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}
|
* @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(
|
public static X509Certificate generateSignedCertificate(
|
||||||
X500Principal principal,
|
X500Principal principal,
|
||||||
GeneralNames subjectAltNames,
|
GeneralNames subjectAltNames,
|
||||||
|
@ -180,6 +164,7 @@ public class CertGenUtils {
|
||||||
boolean isCa,
|
boolean isCa,
|
||||||
int days,
|
int days,
|
||||||
String signatureAlgorithm,
|
String signatureAlgorithm,
|
||||||
|
KeyUsage keyUsage,
|
||||||
Set<ExtendedKeyUsage> extendedKeyUsages
|
Set<ExtendedKeyUsage> extendedKeyUsages
|
||||||
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
|
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException {
|
||||||
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||||
|
@ -198,6 +183,7 @@ public class CertGenUtils {
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
signatureAlgorithm,
|
signatureAlgorithm,
|
||||||
|
keyUsage,
|
||||||
extendedKeyUsages
|
extendedKeyUsages
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -223,6 +209,7 @@ public class CertGenUtils {
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
signatureAlgorithm,
|
signatureAlgorithm,
|
||||||
|
null,
|
||||||
Set.of()
|
Set.of()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -237,6 +224,7 @@ public class CertGenUtils {
|
||||||
ZonedDateTime notBefore,
|
ZonedDateTime notBefore,
|
||||||
ZonedDateTime notAfter,
|
ZonedDateTime notAfter,
|
||||||
String signatureAlgorithm,
|
String signatureAlgorithm,
|
||||||
|
KeyUsage keyUsage,
|
||||||
Set<ExtendedKeyUsage> extendedKeyUsages
|
Set<ExtendedKeyUsage> extendedKeyUsages
|
||||||
) throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
|
) throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
|
||||||
final BigInteger serial = CertGenUtils.getSerial();
|
final BigInteger serial = CertGenUtils.getSerial();
|
||||||
|
@ -272,6 +260,11 @@ public class CertGenUtils {
|
||||||
}
|
}
|
||||||
builder.addExtension(Extension.basicConstraints, isCa, new BasicConstraints(isCa));
|
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) {
|
if (extendedKeyUsages != null) {
|
||||||
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
|
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
|
||||||
builder.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
|
builder.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
|
||||||
|
@ -318,7 +311,7 @@ public class CertGenUtils {
|
||||||
*/
|
*/
|
||||||
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws IOException,
|
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws IOException,
|
||||||
OperatorCreationException {
|
OperatorCreationException {
|
||||||
return generateCSR(keyPair, principal, sanList, Set.of());
|
return generateCSR(keyPair, principal, sanList, null, Set.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -335,6 +328,7 @@ public class CertGenUtils {
|
||||||
KeyPair keyPair,
|
KeyPair keyPair,
|
||||||
X500Principal principal,
|
X500Principal principal,
|
||||||
GeneralNames sanList,
|
GeneralNames sanList,
|
||||||
|
KeyUsage keyUsage,
|
||||||
Set<ExtendedKeyUsage> extendedKeyUsages
|
Set<ExtendedKeyUsage> extendedKeyUsages
|
||||||
) throws IOException, OperatorCreationException {
|
) throws IOException, OperatorCreationException {
|
||||||
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
Objects.requireNonNull(keyPair, "Key-Pair must not be null");
|
||||||
|
@ -347,7 +341,9 @@ public class CertGenUtils {
|
||||||
if (sanList != null) {
|
if (sanList != null) {
|
||||||
extGen.addExtension(Extension.subjectAlternativeName, false, sanList);
|
extGen.addExtension(Extension.subjectAlternativeName, false, sanList);
|
||||||
}
|
}
|
||||||
|
if (keyUsage != null) {
|
||||||
|
extGen.addExtension(Extension.keyUsage, true, keyUsage);
|
||||||
|
}
|
||||||
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
|
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) {
|
||||||
extGen.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
|
extGen.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage);
|
||||||
}
|
}
|
||||||
|
@ -430,4 +426,31 @@ public class CertGenUtils {
|
||||||
public static String buildDnFromDomain(String domain) {
|
public static String buildDnFromDomain(String domain) {
|
||||||
return "DC=" + domain.replace(".", ",DC=");
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -403,7 +403,7 @@ class CertificateGenerateTool extends EnvironmentAwareCommand {
|
||||||
// generate the CA keys and cert
|
// generate the CA keys and cert
|
||||||
X500Principal x500Principal = new X500Principal(dn);
|
X500Principal x500Principal = new X500Principal(dn);
|
||||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
||||||
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days);
|
Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, days, null);
|
||||||
final char[] password;
|
final char[] password;
|
||||||
if (prompt) {
|
if (prompt) {
|
||||||
password = terminal.readSecret("Enter password for CA private key: ");
|
password = terminal.readSecret("Enter password for CA private key: ");
|
||||||
|
|
|
@ -15,6 +15,7 @@ import joptsimple.OptionSpecBuilder;
|
||||||
import org.bouncycastle.asn1.DERIA5String;
|
import org.bouncycastle.asn1.DERIA5String;
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.openssl.PEMEncryptor;
|
import org.bouncycastle.openssl.PEMEncryptor;
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||||
|
@ -110,6 +111,7 @@ class CertificateTool extends MultiCommand {
|
||||||
"[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}"
|
"[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1," + MAX_FILENAME_LENGTH + "}"
|
||||||
);
|
);
|
||||||
private static final int DEFAULT_KEY_SIZE = 2048;
|
private static final int DEFAULT_KEY_SIZE = 2048;
|
||||||
|
static final List<String> DEFAULT_CA_KEY_USAGE = List.of("keyCertSign", "cRLSign");
|
||||||
|
|
||||||
// Older versions of OpenSSL had a max internal password length.
|
// Older versions of OpenSSL had a max internal password length.
|
||||||
// We issue warnings when writing files with passwords that would not be usable in those versions of OpenSSL.
|
// We issue warnings when writing files with passwords that would not be usable in those versions of OpenSSL.
|
||||||
|
@ -202,6 +204,7 @@ class CertificateTool extends MultiCommand {
|
||||||
final OptionSpec<String> outputPathSpec;
|
final OptionSpec<String> outputPathSpec;
|
||||||
final OptionSpec<String> outputPasswordSpec;
|
final OptionSpec<String> outputPasswordSpec;
|
||||||
final OptionSpec<Integer> keysizeSpec;
|
final OptionSpec<Integer> keysizeSpec;
|
||||||
|
OptionSpec<String> caKeyUsageSpec;
|
||||||
|
|
||||||
OptionSpec<Void> pemFormatSpec;
|
OptionSpec<Void> pemFormatSpec;
|
||||||
OptionSpec<Integer> daysSpec;
|
OptionSpec<Integer> daysSpec;
|
||||||
|
@ -274,6 +277,16 @@ class CertificateTool extends MultiCommand {
|
||||||
inputFileSpec = parser.accepts("in", "file containing details of the instances in yaml format").withRequiredArg();
|
inputFileSpec = parser.accepts("in", "file containing details of the instances in yaml format").withRequiredArg();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final void acceptCertificateAuthorityKeyUsage() {
|
||||||
|
caKeyUsageSpec = parser.accepts(
|
||||||
|
"keyusage",
|
||||||
|
"comma separated key usages to use for the generated CA. "
|
||||||
|
+ "defaults to '"
|
||||||
|
+ Strings.collectionToCommaDelimitedString(DEFAULT_CA_KEY_USAGE)
|
||||||
|
+ "'"
|
||||||
|
).withRequiredArg();
|
||||||
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
OptionParser getParser() {
|
OptionParser getParser() {
|
||||||
return parser;
|
return parser;
|
||||||
|
@ -309,6 +322,23 @@ class CertificateTool extends MultiCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<String> getCaKeyUsage(OptionSet options) {
|
||||||
|
if (options.has(caKeyUsageSpec)) {
|
||||||
|
final Function<String, Stream<? extends String>> splitByComma = v -> Stream.of(Strings.splitStringByCommaToArray(v));
|
||||||
|
final List<String> caKeyUsage = caKeyUsageSpec.values(options)
|
||||||
|
.stream()
|
||||||
|
.flatMap(splitByComma)
|
||||||
|
.filter(v -> false == Strings.isNullOrEmpty(v))
|
||||||
|
.toList();
|
||||||
|
if (caKeyUsage.isEmpty()) {
|
||||||
|
return DEFAULT_CA_KEY_USAGE;
|
||||||
|
}
|
||||||
|
return caKeyUsage;
|
||||||
|
} else {
|
||||||
|
return DEFAULT_CA_KEY_USAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final int getDays(OptionSet options) {
|
final int getDays(OptionSet options) {
|
||||||
if (options.has(daysSpec)) {
|
if (options.has(daysSpec)) {
|
||||||
return daysSpec.value(options);
|
return daysSpec.value(options);
|
||||||
|
@ -396,7 +426,8 @@ class CertificateTool extends MultiCommand {
|
||||||
}
|
}
|
||||||
X500Principal x500Principal = new X500Principal(dn);
|
X500Principal x500Principal = new X500Principal(dn);
|
||||||
KeyPair keyPair = CertGenUtils.generateKeyPair(getKeySize(options));
|
KeyPair keyPair = CertGenUtils.generateKeyPair(getKeySize(options));
|
||||||
X509Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, getDays(options));
|
final KeyUsage caKeyUsage = CertGenUtils.buildKeyUsage(getCaKeyUsage(options));
|
||||||
|
X509Certificate caCert = CertGenUtils.generateCACertificate(x500Principal, keyPair, getDays(options), caKeyUsage);
|
||||||
|
|
||||||
if (options.hasArgument(caPasswordSpec)) {
|
if (options.hasArgument(caPasswordSpec)) {
|
||||||
char[] password = getChars(caPasswordSpec.value(options));
|
char[] password = getChars(caPasswordSpec.value(options));
|
||||||
|
@ -933,9 +964,7 @@ class CertificateTool extends MultiCommand {
|
||||||
keyPair,
|
keyPair,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
false,
|
days
|
||||||
days,
|
|
||||||
null
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new CertificateAndKey((X509Certificate) certificate, keyPair.getPrivate());
|
return new CertificateAndKey((X509Certificate) certificate, keyPair.getPrivate());
|
||||||
|
@ -949,6 +978,7 @@ class CertificateTool extends MultiCommand {
|
||||||
super("generate a new local certificate authority");
|
super("generate a new local certificate authority");
|
||||||
acceptCertificateGenerationOptions();
|
acceptCertificateGenerationOptions();
|
||||||
acceptsCertificateAuthorityName();
|
acceptsCertificateAuthorityName();
|
||||||
|
acceptCertificateAuthorityKeyUsage();
|
||||||
super.caPasswordSpec = super.outputPasswordSpec;
|
super.caPasswordSpec = super.outputPasswordSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,8 @@ import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -80,7 +82,9 @@ import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtils.buildKeyUsage;
|
||||||
import static org.elasticsearch.xpack.security.cli.CertGenUtils.generateSignedCertificate;
|
import static org.elasticsearch.xpack.security.cli.CertGenUtils.generateSignedCertificate;
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtils.isValidKeyUsage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This command is the "elasticsearch-certutil http" command. It provides a guided process for creating
|
* This command is the "elasticsearch-certutil http" command. It provides a guided process for creating
|
||||||
|
@ -95,7 +99,8 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
static final X500Principal DEFAULT_CA_NAME = new X500Principal("CN=Elasticsearch HTTP CA");
|
static final X500Principal DEFAULT_CA_NAME = new X500Principal("CN=Elasticsearch HTTP CA");
|
||||||
static final int DEFAULT_CA_KEY_SIZE = DEFAULT_CERT_KEY_SIZE;
|
static final int DEFAULT_CA_KEY_SIZE = DEFAULT_CERT_KEY_SIZE;
|
||||||
static final Period DEFAULT_CA_VALIDITY = DEFAULT_CERT_VALIDITY;
|
static final Period DEFAULT_CA_VALIDITY = DEFAULT_CERT_VALIDITY;
|
||||||
|
static final List<String> DEFAULT_CA_KEY_USAGE = List.of("keyCertSign", "cRLSign");
|
||||||
|
static final List<String> DEFAULT_CERT_KEY_USAGE = List.of("digitalSignature", "keyEncipherment");
|
||||||
private static final String ES_README_CSR = "es-readme-csr.txt";
|
private static final String ES_README_CSR = "es-readme-csr.txt";
|
||||||
private static final String ES_YML_CSR = "es-sample-csr.yml";
|
private static final String ES_YML_CSR = "es-sample-csr.yml";
|
||||||
private static final String ES_README_P12 = "es-readme-p12.txt";
|
private static final String ES_README_P12 = "es-readme-p12.txt";
|
||||||
|
@ -133,14 +138,24 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
final List<String> dnsNames;
|
final List<String> dnsNames;
|
||||||
final List<String> ipNames;
|
final List<String> ipNames;
|
||||||
final int keySize;
|
final int keySize;
|
||||||
|
final List<String> keyUsage;
|
||||||
final Period validity;
|
final Period validity;
|
||||||
|
|
||||||
private CertOptions(String name, X500Principal subject, List<String> dnsNames, List<String> ipNames, int keySize, Period validity) {
|
private CertOptions(
|
||||||
|
String name,
|
||||||
|
X500Principal subject,
|
||||||
|
List<String> dnsNames,
|
||||||
|
List<String> ipNames,
|
||||||
|
int keySize,
|
||||||
|
List<String> keyUsage,
|
||||||
|
Period validity
|
||||||
|
) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.subject = subject;
|
this.subject = subject;
|
||||||
this.dnsNames = dnsNames;
|
this.dnsNames = dnsNames;
|
||||||
this.ipNames = ipNames;
|
this.ipNames = ipNames;
|
||||||
this.keySize = keySize;
|
this.keySize = keySize;
|
||||||
|
this.keyUsage = keyUsage;
|
||||||
this.validity = validity;
|
this.validity = validity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,6 +209,7 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "\tDNS Names: " + Strings.collectionToCommaDelimitedString(cert.dnsNames));
|
terminal.println(Terminal.Verbosity.VERBOSE, "\tDNS Names: " + Strings.collectionToCommaDelimitedString(cert.dnsNames));
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "\tIP Names: " + Strings.collectionToCommaDelimitedString(cert.ipNames));
|
terminal.println(Terminal.Verbosity.VERBOSE, "\tIP Names: " + Strings.collectionToCommaDelimitedString(cert.ipNames));
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "\tKey Size: " + cert.keySize);
|
terminal.println(Terminal.Verbosity.VERBOSE, "\tKey Size: " + cert.keySize);
|
||||||
|
terminal.println(Terminal.Verbosity.VERBOSE, "\tKey Usage: " + Strings.collectionToCommaDelimitedString(cert.keyUsage));
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "\tValidity: " + toString(cert.validity));
|
terminal.println(Terminal.Verbosity.VERBOSE, "\tValidity: " + toString(cert.validity));
|
||||||
certificates.add(cert);
|
certificates.add(cert);
|
||||||
|
|
||||||
|
@ -339,6 +355,7 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
keyPair,
|
keyPair,
|
||||||
cert.subject,
|
cert.subject,
|
||||||
sanList,
|
sanList,
|
||||||
|
buildKeyUsage(cert.keyUsage),
|
||||||
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
|
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
|
||||||
);
|
);
|
||||||
final String csrFile = "http-" + cert.name + ".csr";
|
final String csrFile = "http-" + cert.name + ".csr";
|
||||||
|
@ -372,6 +389,7 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
null,
|
null,
|
||||||
|
buildKeyUsage(cert.keyUsage),
|
||||||
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
|
Set.of(new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -692,10 +710,12 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
}
|
}
|
||||||
X500Principal dn = buildDistinguishedName(certName);
|
X500Principal dn = buildDistinguishedName(certName);
|
||||||
int keySize = DEFAULT_CERT_KEY_SIZE;
|
int keySize = DEFAULT_CERT_KEY_SIZE;
|
||||||
|
List<String> keyUsage = DEFAULT_CERT_KEY_USAGE;
|
||||||
while (true) {
|
while (true) {
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "Key Name: " + certName);
|
terminal.println(Terminal.Verbosity.SILENT, "Key Name: " + certName);
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "Subject DN: " + dn);
|
terminal.println(Terminal.Verbosity.SILENT, "Subject DN: " + dn);
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "Key Size: " + keySize);
|
terminal.println(Terminal.Verbosity.SILENT, "Key Size: " + keySize);
|
||||||
|
terminal.println(Terminal.Verbosity.SILENT, "Key Usage: " + Strings.collectionToCommaDelimitedString(keyUsage));
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "");
|
terminal.println(Terminal.Verbosity.SILENT, "");
|
||||||
if (terminal.promptYesNo("Do you wish to change any of these options?", false) == false) {
|
if (terminal.promptYesNo("Do you wish to change any of these options?", false) == false) {
|
||||||
break;
|
break;
|
||||||
|
@ -736,9 +756,22 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
|
|
||||||
keySize = readKeySize(terminal, keySize);
|
keySize = readKeySize(terminal, keySize);
|
||||||
terminal.println("");
|
terminal.println("");
|
||||||
|
|
||||||
|
printHeader("What key usage should your certificate have?", terminal);
|
||||||
|
terminal.println("The key usage extension defines the purpose of the key contained in the certificate.");
|
||||||
|
terminal.println("The usage restriction might be employed when a key, that could be used for more than ");
|
||||||
|
terminal.println("one operation, is to be restricted.");
|
||||||
|
terminal.println("You may enter the key usage as a comma-delimited list of following values: ");
|
||||||
|
for (String keyUsageName : CertGenUtils.KEY_USAGE_MAPPINGS.keySet()) {
|
||||||
|
terminal.println(" - " + keyUsageName);
|
||||||
|
}
|
||||||
|
terminal.println("");
|
||||||
|
|
||||||
|
keyUsage = readKeyUsage(terminal, keyUsage);
|
||||||
|
terminal.println("");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CertOptions(certName, dn, dnsNames, ipNames, keySize, validity);
|
return new CertOptions(certName, dn, dnsNames, ipNames, keySize, keyUsage, validity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String validateHostname(String name) {
|
private static String validateHostname(String name) {
|
||||||
|
@ -859,10 +892,12 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
X500Principal dn = DEFAULT_CA_NAME;
|
X500Principal dn = DEFAULT_CA_NAME;
|
||||||
Period validity = DEFAULT_CA_VALIDITY;
|
Period validity = DEFAULT_CA_VALIDITY;
|
||||||
int keySize = DEFAULT_CA_KEY_SIZE;
|
int keySize = DEFAULT_CA_KEY_SIZE;
|
||||||
|
List<String> keyUsage = DEFAULT_CA_KEY_USAGE;
|
||||||
while (true) {
|
while (true) {
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "Subject DN: " + dn);
|
terminal.println(Terminal.Verbosity.SILENT, "Subject DN: " + dn);
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "Validity: " + toString(validity));
|
terminal.println(Terminal.Verbosity.SILENT, "Validity: " + toString(validity));
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "Key Size: " + keySize);
|
terminal.println(Terminal.Verbosity.SILENT, "Key Size: " + keySize);
|
||||||
|
terminal.println(Terminal.Verbosity.SILENT, "Key Usage: " + Strings.collectionToCommaDelimitedString(keyUsage));
|
||||||
terminal.println(Terminal.Verbosity.SILENT, "");
|
terminal.println(Terminal.Verbosity.SILENT, "");
|
||||||
if (terminal.promptYesNo("Do you wish to change any of these options?", false) == false) {
|
if (terminal.promptYesNo("Do you wish to change any of these options?", false) == false) {
|
||||||
break;
|
break;
|
||||||
|
@ -904,13 +939,38 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
|
|
||||||
keySize = readKeySize(terminal, keySize);
|
keySize = readKeySize(terminal, keySize);
|
||||||
terminal.println("");
|
terminal.println("");
|
||||||
|
|
||||||
|
printHeader("What key usage should your CA have?", terminal);
|
||||||
|
terminal.println("The key usage extension defines the purpose of the key contained in the certificate.");
|
||||||
|
terminal.println("The usage restriction might be employed when a key, that could be used for more than ");
|
||||||
|
terminal.println("one operation, is to be restricted.");
|
||||||
|
terminal.println("You may enter the key usage as a comma-delimited list of following values: ");
|
||||||
|
for (String keyUsageName : CertGenUtils.KEY_USAGE_MAPPINGS.keySet()) {
|
||||||
|
terminal.println(" - " + keyUsageName);
|
||||||
|
}
|
||||||
|
terminal.println("");
|
||||||
|
|
||||||
|
keyUsage = readKeyUsage(terminal, keyUsage);
|
||||||
|
terminal.println("");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
|
final KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
|
||||||
final ZonedDateTime notBefore = ZonedDateTime.now(ZoneOffset.UTC);
|
final ZonedDateTime notBefore = ZonedDateTime.now(ZoneOffset.UTC);
|
||||||
final ZonedDateTime notAfter = notBefore.plus(validity);
|
final ZonedDateTime notAfter = notBefore.plus(validity);
|
||||||
X509Certificate caCert = generateSignedCertificate(dn, null, keyPair, null, null, true, notBefore, notAfter, null);
|
X509Certificate caCert = generateSignedCertificate(
|
||||||
|
dn,
|
||||||
|
null,
|
||||||
|
keyPair,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
notBefore,
|
||||||
|
notAfter,
|
||||||
|
null,
|
||||||
|
buildKeyUsage(keyUsage),
|
||||||
|
Set.of()
|
||||||
|
);
|
||||||
|
|
||||||
printHeader("CA password", terminal);
|
printHeader("CA password", terminal);
|
||||||
terminal.println("We recommend that you protect your CA private key with a strong password.");
|
terminal.println("We recommend that you protect your CA private key with a strong password.");
|
||||||
|
@ -979,6 +1039,31 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> readKeyUsage(Terminal terminal, List<String> defaultKeyUsage) {
|
||||||
|
return tryReadInput(terminal, "Key Usage", defaultKeyUsage, input -> {
|
||||||
|
final String[] keyUsages = input.split(",");
|
||||||
|
final List<String> resolvedKeyUsages = new ArrayList<>(keyUsages.length);
|
||||||
|
for (String keyUsage : keyUsages) {
|
||||||
|
keyUsage = keyUsage.trim();
|
||||||
|
if (keyUsage.isEmpty()) {
|
||||||
|
terminal.println("Key usage cannot be blank or empty");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isValidKeyUsage(keyUsage) == false) {
|
||||||
|
terminal.println("Invalid key usage: " + keyUsage);
|
||||||
|
terminal.println("The key usage should be one of the following values: ");
|
||||||
|
for (String keyUsageName : CertGenUtils.KEY_USAGE_MAPPINGS.keySet()) {
|
||||||
|
terminal.println(" - " + keyUsageName);
|
||||||
|
}
|
||||||
|
terminal.println("");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
resolvedKeyUsages.add(keyUsage);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(resolvedKeyUsages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static char[] readPassword(Terminal terminal, String prompt, boolean confirm) {
|
private static char[] readPassword(Terminal terminal, String prompt, boolean confirm) {
|
||||||
while (true) {
|
while (true) {
|
||||||
final char[] password = terminal.readSecret(prompt + " [<ENTER> for none]");
|
final char[] password = terminal.readSecret(prompt + " [<ENTER> for none]");
|
||||||
|
@ -1080,7 +1165,14 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T tryReadInput(Terminal terminal, String prompt, T defaultValue, Function<String, T> parser) {
|
private static <T> T tryReadInput(Terminal terminal, String prompt, T defaultValue, Function<String, T> parser) {
|
||||||
final String defaultStr = defaultValue instanceof Period ? toString((Period) defaultValue) : String.valueOf(defaultValue);
|
final String defaultStr;
|
||||||
|
if (defaultValue instanceof Period) {
|
||||||
|
defaultStr = toString((Period) defaultValue);
|
||||||
|
} else if (defaultValue instanceof Collection<?> collection) {
|
||||||
|
defaultStr = Strings.collectionToCommaDelimitedString(collection);
|
||||||
|
} else {
|
||||||
|
defaultStr = String.valueOf(defaultValue);
|
||||||
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
final String input = terminal.readText(prompt + " [" + defaultStr + "] ");
|
final String input = terminal.readText(prompt + " [" + defaultStr + "] ");
|
||||||
if (Strings.isEmpty(input)) {
|
if (Strings.isEmpty(input)) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.AUTO_CONFIG
|
||||||
import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.AUTO_CONFIG_TRANSPORT_ALT_DN;
|
import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.AUTO_CONFIG_TRANSPORT_ALT_DN;
|
||||||
import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.anyRemoteHostNodeAddress;
|
import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.anyRemoteHostNodeAddress;
|
||||||
import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.removePreviousAutoconfiguration;
|
import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.removePreviousAutoconfiguration;
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtilsTests.assertExpectedKeyUsage;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@ -149,7 +150,7 @@ public class AutoConfigureNodeTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGeneratedHTTPCertificateSANs() throws Exception {
|
public void testGeneratedHTTPCertificateSANsAndKeyUsage() throws Exception {
|
||||||
// test no publish settings
|
// test no publish settings
|
||||||
Path tempDir = createTempDir();
|
Path tempDir = createTempDir();
|
||||||
try {
|
try {
|
||||||
|
@ -180,7 +181,7 @@ public class AutoConfigureNodeTests extends ESTestCase {
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "localhost", GeneralName.dNSName), is(true));
|
assertThat(checkGeneralNameSan(httpCertificate, "localhost", GeneralName.dNSName), is(true));
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "172.168.1.100", GeneralName.iPAddress), is(true));
|
assertThat(checkGeneralNameSan(httpCertificate, "172.168.1.100", GeneralName.iPAddress), is(true));
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "10.10.10.100", GeneralName.iPAddress), is(false));
|
assertThat(checkGeneralNameSan(httpCertificate, "10.10.10.100", GeneralName.iPAddress), is(false));
|
||||||
verifyExtendedKeyUsage(httpCertificate);
|
verifyKeyUsageAndExtendedKeyUsage(httpCertificate);
|
||||||
} finally {
|
} finally {
|
||||||
deleteDirectory(tempDir);
|
deleteDirectory(tempDir);
|
||||||
}
|
}
|
||||||
|
@ -202,7 +203,7 @@ public class AutoConfigureNodeTests extends ESTestCase {
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "localhost", GeneralName.dNSName), is(true));
|
assertThat(checkGeneralNameSan(httpCertificate, "localhost", GeneralName.dNSName), is(true));
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "172.168.1.100", GeneralName.iPAddress), is(false));
|
assertThat(checkGeneralNameSan(httpCertificate, "172.168.1.100", GeneralName.iPAddress), is(false));
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "10.10.10.100", GeneralName.iPAddress), is(true));
|
assertThat(checkGeneralNameSan(httpCertificate, "10.10.10.100", GeneralName.iPAddress), is(true));
|
||||||
verifyExtendedKeyUsage(httpCertificate);
|
verifyKeyUsageAndExtendedKeyUsage(httpCertificate);
|
||||||
} finally {
|
} finally {
|
||||||
deleteDirectory(tempDir);
|
deleteDirectory(tempDir);
|
||||||
}
|
}
|
||||||
|
@ -228,7 +229,7 @@ public class AutoConfigureNodeTests extends ESTestCase {
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "balkan.beast", GeneralName.dNSName), is(true));
|
assertThat(checkGeneralNameSan(httpCertificate, "balkan.beast", GeneralName.dNSName), is(true));
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "172.168.1.100", GeneralName.iPAddress), is(false));
|
assertThat(checkGeneralNameSan(httpCertificate, "172.168.1.100", GeneralName.iPAddress), is(false));
|
||||||
assertThat(checkGeneralNameSan(httpCertificate, "10.10.10.100", GeneralName.iPAddress), is(false));
|
assertThat(checkGeneralNameSan(httpCertificate, "10.10.10.100", GeneralName.iPAddress), is(false));
|
||||||
verifyExtendedKeyUsage(httpCertificate);
|
verifyKeyUsageAndExtendedKeyUsage(httpCertificate);
|
||||||
} finally {
|
} finally {
|
||||||
deleteDirectory(tempDir);
|
deleteDirectory(tempDir);
|
||||||
}
|
}
|
||||||
|
@ -288,11 +289,12 @@ public class AutoConfigureNodeTests extends ESTestCase {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyExtendedKeyUsage(X509Certificate httpCertificate) throws Exception {
|
private void verifyKeyUsageAndExtendedKeyUsage(X509Certificate httpCertificate) throws Exception {
|
||||||
List<String> extendedKeyUsage = httpCertificate.getExtendedKeyUsage();
|
List<String> extendedKeyUsage = httpCertificate.getExtendedKeyUsage();
|
||||||
assertEquals("Only one extended key usage expected for HTTP certificate.", 1, extendedKeyUsage.size());
|
assertEquals("Only one extended key usage expected for HTTP certificate.", 1, extendedKeyUsage.size());
|
||||||
String expectedServerAuthUsage = KeyPurposeId.id_kp_serverAuth.toASN1Primitive().toString();
|
String expectedServerAuthUsage = KeyPurposeId.id_kp_serverAuth.toASN1Primitive().toString();
|
||||||
assertEquals("Expected serverAuth extended key usage.", expectedServerAuthUsage, extendedKeyUsage.get(0));
|
assertEquals("Expected serverAuth extended key usage.", expectedServerAuthUsage, extendedKeyUsage.get(0));
|
||||||
|
assertExpectedKeyUsage(httpCertificate, HttpCertificateCommand.DEFAULT_CERT_KEY_USAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private X509Certificate runAutoConfigAndReturnHTTPCertificate(Path configDir, Settings settings) throws Exception {
|
private X509Certificate runAutoConfigAndReturnHTTPCertificate(Path configDir, Settings settings) throws Exception {
|
||||||
|
|
|
@ -7,10 +7,13 @@
|
||||||
|
|
||||||
package org.elasticsearch.xpack.security.cli;
|
package org.elasticsearch.xpack.security.cli;
|
||||||
|
|
||||||
|
import com.unboundid.util.ssl.cert.KeyUsageExtension;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||||
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
import org.elasticsearch.common.network.InetAddresses;
|
import org.elasticsearch.common.network.InetAddresses;
|
||||||
import org.elasticsearch.common.network.NetworkAddress;
|
import org.elasticsearch.common.network.NetworkAddress;
|
||||||
import org.elasticsearch.core.SuppressForbidden;
|
import org.elasticsearch.core.SuppressForbidden;
|
||||||
|
@ -27,22 +30,57 @@ import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtils.KEY_USAGE_MAPPINGS;
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtils.buildKeyUsage;
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtils.isValidKeyUsage;
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for cert utils
|
* Unit tests for cert utils
|
||||||
*/
|
*/
|
||||||
public class CertGenUtilsTests extends ESTestCase {
|
public class CertGenUtilsTests extends ESTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mapping of key usage names to their corresponding bit index as defined in {@code KeyUsage} class:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>digitalSignature (0)</li>
|
||||||
|
* <li>nonRepudiation (1)</li>
|
||||||
|
* <li>keyEncipherment (2)</li>
|
||||||
|
* <li>dataEncipherment (3)</li>
|
||||||
|
* <li>keyAgreement (4)</li>
|
||||||
|
* <li>keyCertSign (5)</li>
|
||||||
|
* <li>cRLSign (6)</li>
|
||||||
|
* <li>encipherOnly (7)</li>
|
||||||
|
* <li>decipherOnly (8)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private static final Map<String, Integer> KEY_USAGE_BITS = Map.ofEntries(
|
||||||
|
Map.entry("digitalSignature", 0),
|
||||||
|
Map.entry("nonRepudiation", 1),
|
||||||
|
Map.entry("keyEncipherment", 2),
|
||||||
|
Map.entry("dataEncipherment", 3),
|
||||||
|
Map.entry("keyAgreement", 4),
|
||||||
|
Map.entry("keyCertSign", 5),
|
||||||
|
Map.entry("cRLSign", 6),
|
||||||
|
Map.entry("encipherOnly", 7),
|
||||||
|
Map.entry("decipherOnly", 8)
|
||||||
|
);
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void muteInFips() {
|
public static void muteInFips() {
|
||||||
assumeFalse("Can't run in a FIPS JVM", inFipsJvm());
|
assumeFalse("Can't run in a FIPS JVM", inFipsJvm());
|
||||||
|
@ -103,6 +141,7 @@ public class CertGenUtilsTests extends ESTestCase {
|
||||||
// root CA
|
// root CA
|
||||||
final X500Principal rootCaPrincipal = new X500Principal("DC=example.com");
|
final X500Principal rootCaPrincipal = new X500Principal("DC=example.com");
|
||||||
final KeyPair rootCaKeyPair = CertGenUtils.generateKeyPair(2048);
|
final KeyPair rootCaKeyPair = CertGenUtils.generateKeyPair(2048);
|
||||||
|
final List<String> rootCaKeyUsages = List.of("keyCertSign", "cRLSign");
|
||||||
final X509Certificate rootCaCert = CertGenUtils.generateSignedCertificate(
|
final X509Certificate rootCaCert = CertGenUtils.generateSignedCertificate(
|
||||||
rootCaPrincipal,
|
rootCaPrincipal,
|
||||||
null,
|
null,
|
||||||
|
@ -112,12 +151,15 @@ public class CertGenUtilsTests extends ESTestCase {
|
||||||
true,
|
true,
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
null
|
null,
|
||||||
|
buildKeyUsage(rootCaKeyUsages),
|
||||||
|
Set.of()
|
||||||
);
|
);
|
||||||
|
|
||||||
// sub CA
|
// sub CA
|
||||||
final X500Principal subCaPrincipal = new X500Principal("DC=Sub CA,DC=example.com");
|
final X500Principal subCaPrincipal = new X500Principal("DC=Sub CA,DC=example.com");
|
||||||
final KeyPair subCaKeyPair = CertGenUtils.generateKeyPair(2048);
|
final KeyPair subCaKeyPair = CertGenUtils.generateKeyPair(2048);
|
||||||
|
final List<String> subCaKeyUsage = List.of("digitalSignature", "keyCertSign", "cRLSign");
|
||||||
final X509Certificate subCaCert = CertGenUtils.generateSignedCertificate(
|
final X509Certificate subCaCert = CertGenUtils.generateSignedCertificate(
|
||||||
subCaPrincipal,
|
subCaPrincipal,
|
||||||
null,
|
null,
|
||||||
|
@ -127,12 +169,15 @@ public class CertGenUtilsTests extends ESTestCase {
|
||||||
true,
|
true,
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
null
|
null,
|
||||||
|
buildKeyUsage(subCaKeyUsage),
|
||||||
|
Set.of()
|
||||||
);
|
);
|
||||||
|
|
||||||
// end entity
|
// end entity
|
||||||
final X500Principal endEntityPrincipal = new X500Principal("CN=TLS Client\\+Server,DC=Sub CA,DC=example.com");
|
final X500Principal endEntityPrincipal = new X500Principal("CN=TLS Client\\+Server,DC=Sub CA,DC=example.com");
|
||||||
final KeyPair endEntityKeyPair = CertGenUtils.generateKeyPair(2048);
|
final KeyPair endEntityKeyPair = CertGenUtils.generateKeyPair(2048);
|
||||||
|
final List<String> endEntityKeyUsage = randomBoolean() ? null : List.of("digitalSignature", "keyEncipherment");
|
||||||
final X509Certificate endEntityCert = CertGenUtils.generateSignedCertificate(
|
final X509Certificate endEntityCert = CertGenUtils.generateSignedCertificate(
|
||||||
endEntityPrincipal,
|
endEntityPrincipal,
|
||||||
null,
|
null,
|
||||||
|
@ -143,6 +188,7 @@ public class CertGenUtilsTests extends ESTestCase {
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter,
|
notAfter,
|
||||||
null,
|
null,
|
||||||
|
buildKeyUsage(endEntityKeyUsage),
|
||||||
Set.of(new ExtendedKeyUsage(KeyPurposeId.anyExtendedKeyUsage))
|
Set.of(new ExtendedKeyUsage(KeyPurposeId.anyExtendedKeyUsage))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -162,6 +208,101 @@ public class CertGenUtilsTests extends ESTestCase {
|
||||||
trustStore.setCertificateEntry("trustAnchor", rootCaCert); // anchor: any part of the chain, or issuer of last entry in chain
|
trustStore.setCertificateEntry("trustAnchor", rootCaCert); // anchor: any part of the chain, or issuer of last entry in chain
|
||||||
|
|
||||||
validateEndEntityTlsChain(trustStore, certChain, true, true);
|
validateEndEntityTlsChain(trustStore, certChain, true, true);
|
||||||
|
|
||||||
|
// verify custom key usages
|
||||||
|
assertExpectedKeyUsage(rootCaCert, rootCaKeyUsages);
|
||||||
|
assertExpectedKeyUsage(subCaCert, subCaKeyUsage);
|
||||||
|
// when key usage is not specified, the key usage bits should be null
|
||||||
|
if (endEntityKeyUsage == null) {
|
||||||
|
assertThat(endEntityCert.getKeyUsage(), is(nullValue()));
|
||||||
|
assertThat(endEntityCert.getCriticalExtensionOIDs().contains(KeyUsageExtension.KEY_USAGE_OID.toString()), is(false));
|
||||||
|
} else {
|
||||||
|
assertExpectedKeyUsage(endEntityCert, endEntityKeyUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBuildKeyUsage() {
|
||||||
|
// sanity check that lookup maps are containing the same keyUsage entries
|
||||||
|
assertThat(KEY_USAGE_BITS.keySet(), containsInAnyOrder(KEY_USAGE_MAPPINGS.keySet().toArray()));
|
||||||
|
|
||||||
|
// passing null or empty list of keyUsage names should return null
|
||||||
|
assertThat(buildKeyUsage(null), is(nullValue()));
|
||||||
|
assertThat(buildKeyUsage(List.of()), is(nullValue()));
|
||||||
|
|
||||||
|
// invalid names should throw IAE
|
||||||
|
var e = expectThrows(IllegalArgumentException.class, () -> buildKeyUsage(List.of(randomAlphanumericOfLength(5))));
|
||||||
|
assertThat(e.getMessage(), containsString("Unknown keyUsage"));
|
||||||
|
|
||||||
|
{
|
||||||
|
final List<String> keyUsages = randomNonEmptySubsetOf(KEY_USAGE_MAPPINGS.keySet());
|
||||||
|
final KeyUsage keyUsage = buildKeyUsage(keyUsages);
|
||||||
|
for (String usageName : keyUsages) {
|
||||||
|
final Integer usage = KEY_USAGE_MAPPINGS.get(usageName);
|
||||||
|
assertThat(" mapping for keyUsage [" + usageName + "] is missing", usage, is(notNullValue()));
|
||||||
|
assertThat("expected keyUsage [" + usageName + "] to be set in [" + keyUsage + "]", keyUsage.hasUsages(usage), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> keyUsagesNotSet = KEY_USAGE_MAPPINGS.keySet()
|
||||||
|
.stream()
|
||||||
|
.filter(u -> keyUsages.contains(u) == false)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
for (String usageName : keyUsagesNotSet) {
|
||||||
|
final Integer usage = KEY_USAGE_MAPPINGS.get(usageName);
|
||||||
|
assertThat(" mapping for keyUsage [" + usageName + "] is missing", usage, is(notNullValue()));
|
||||||
|
assertThat(
|
||||||
|
"expected keyUsage [" + usageName + "] not to be set in [" + keyUsage + "]",
|
||||||
|
keyUsage.hasUsages(usage),
|
||||||
|
is(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// test that duplicates and whitespaces are ignored
|
||||||
|
KeyUsage keyUsage = buildKeyUsage(
|
||||||
|
List.of("digitalSignature ", " nonRepudiation", "\tkeyEncipherment", "keyEncipherment\n")
|
||||||
|
);
|
||||||
|
assertThat(keyUsage.hasUsages(KEY_USAGE_MAPPINGS.get("digitalSignature")), is(true));
|
||||||
|
assertThat(keyUsage.hasUsages(KEY_USAGE_MAPPINGS.get("nonRepudiation")), is(true));
|
||||||
|
assertThat(keyUsage.hasUsages(KEY_USAGE_MAPPINGS.get("digitalSignature")), is(true));
|
||||||
|
assertThat(keyUsage.hasUsages(KEY_USAGE_MAPPINGS.get("keyEncipherment")), is(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIsValidKeyUsage() {
|
||||||
|
assertThat(isValidKeyUsage(randomFrom(KEY_USAGE_MAPPINGS.keySet())), is(true));
|
||||||
|
assertThat(isValidKeyUsage(randomAlphanumericOfLength(5)), is(false));
|
||||||
|
|
||||||
|
// keyUsage names are case-sensitive
|
||||||
|
assertThat(isValidKeyUsage("DigitalSignature"), is(false));
|
||||||
|
|
||||||
|
// white-spaces are ignored
|
||||||
|
assertThat(isValidKeyUsage("keyAgreement "), is(true));
|
||||||
|
assertThat(isValidKeyUsage("keyCertSign\n"), is(true));
|
||||||
|
assertThat(isValidKeyUsage("\tcRLSign "), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertExpectedKeyUsage(X509Certificate certificate, List<String> expectedKeyUsage) {
|
||||||
|
final boolean[] keyUsage = certificate.getKeyUsage();
|
||||||
|
assertThat("Expected " + KEY_USAGE_BITS.size() + " bits for key usage", keyUsage.length, equalTo(KEY_USAGE_BITS.size()));
|
||||||
|
final Set<Integer> expectedBitsToBeSet = expectedKeyUsage.stream().map(KEY_USAGE_BITS::get).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
for (int i = 0; i < keyUsage.length; i++) {
|
||||||
|
if (expectedBitsToBeSet.contains(i)) {
|
||||||
|
assertThat("keyUsage bit [" + i + "] expected to be set: " + expectedKeyUsage, keyUsage[i], equalTo(true));
|
||||||
|
} else {
|
||||||
|
assertThat("keyUsage bit [" + i + "] not expected to be set: " + expectedKeyUsage, keyUsage[i], equalTo(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// key usage must be marked as critical
|
||||||
|
assertThat(
|
||||||
|
"keyUsage extension should be marked as critical",
|
||||||
|
certificate.getCriticalExtensionOIDs().contains(KeyUsageExtension.KEY_USAGE_OID.toString()),
|
||||||
|
is(true)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -274,7 +274,7 @@ public class CertificateGenerateToolTests extends ESTestCase {
|
||||||
final int keysize = randomFrom(1024, 2048);
|
final int keysize = randomFrom(1024, 2048);
|
||||||
final int days = randomIntBetween(1, 1024);
|
final int days = randomIntBetween(1, 1024);
|
||||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
KeyPair keyPair = CertGenUtils.generateKeyPair(keysize);
|
||||||
X509Certificate caCert = CertGenUtils.generateCACertificate(new X500Principal("CN=test ca"), keyPair, days);
|
X509Certificate caCert = CertGenUtils.generateCACertificate(new X500Principal("CN=test ca"), keyPair, days, null);
|
||||||
|
|
||||||
final boolean generatedCa = randomBoolean();
|
final boolean generatedCa = randomBoolean();
|
||||||
final char[] keyPassword = randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() : null;
|
final char[] keyPassword = randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD.toCharArray() : null;
|
||||||
|
|
|
@ -415,7 +415,13 @@ public class CertificateToolTests extends ESTestCase {
|
||||||
int days = randomIntBetween(1, 1024);
|
int days = randomIntBetween(1, 1024);
|
||||||
|
|
||||||
KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
|
KeyPair keyPair = CertGenUtils.generateKeyPair(keySize);
|
||||||
X509Certificate caCert = CertGenUtils.generateCACertificate(new X500Principal("CN=test ca"), keyPair, days);
|
List<String> caKeyUsage = randomBoolean() ? null : CertificateTool.DEFAULT_CA_KEY_USAGE;
|
||||||
|
X509Certificate caCert = CertGenUtils.generateCACertificate(
|
||||||
|
new X500Principal("CN=test ca"),
|
||||||
|
keyPair,
|
||||||
|
days,
|
||||||
|
CertGenUtils.buildKeyUsage(caKeyUsage)
|
||||||
|
);
|
||||||
|
|
||||||
final boolean selfSigned = randomBoolean();
|
final boolean selfSigned = randomBoolean();
|
||||||
final String keyPassword = randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD : null;
|
final String keyPassword = randomBoolean() ? SecuritySettingsSourceField.TEST_PASSWORD : null;
|
||||||
|
@ -1191,6 +1197,7 @@ public class CertificateToolTests extends ESTestCase {
|
||||||
final int caKeySize = randomIntBetween(4, 8) * 512;
|
final int caKeySize = randomIntBetween(4, 8) * 512;
|
||||||
final int days = randomIntBetween(7, 1500);
|
final int days = randomIntBetween(7, 1500);
|
||||||
final String caPassword = randomFrom("", randomAlphaOfLengthBetween(4, 80));
|
final String caPassword = randomFrom("", randomAlphaOfLengthBetween(4, 80));
|
||||||
|
final String caKeyUsage = randomFrom("", Strings.collectionToCommaDelimitedString(CertificateTool.DEFAULT_CA_KEY_USAGE));
|
||||||
|
|
||||||
final CertificateAuthorityCommand caCommand = new PathAwareCertificateAuthorityCommand(caFile);
|
final CertificateAuthorityCommand caCommand = new PathAwareCertificateAuthorityCommand(caFile);
|
||||||
String[] args = {
|
String[] args = {
|
||||||
|
@ -1203,7 +1210,9 @@ public class CertificateToolTests extends ESTestCase {
|
||||||
"-keysize",
|
"-keysize",
|
||||||
String.valueOf(caKeySize),
|
String.valueOf(caKeySize),
|
||||||
"-days",
|
"-days",
|
||||||
String.valueOf(days) };
|
String.valueOf(days),
|
||||||
|
"-keyusage",
|
||||||
|
caKeyUsage };
|
||||||
if (pem) {
|
if (pem) {
|
||||||
args = ArrayUtils.append(args, "--pem");
|
args = ArrayUtils.append(args, "--pem");
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.bouncycastle.asn1.x509.Extensions;
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||||
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
|
@ -30,6 +31,7 @@ import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.elasticsearch.cli.MockTerminal;
|
import org.elasticsearch.cli.MockTerminal;
|
||||||
import org.elasticsearch.cli.ProcessInfo;
|
import org.elasticsearch.cli.ProcessInfo;
|
||||||
import org.elasticsearch.common.CheckedBiFunction;
|
import org.elasticsearch.common.CheckedBiFunction;
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.network.NetworkAddress;
|
import org.elasticsearch.common.network.NetworkAddress;
|
||||||
import org.elasticsearch.common.ssl.PemUtils;
|
import org.elasticsearch.common.ssl.PemUtils;
|
||||||
import org.elasticsearch.core.CheckedFunction;
|
import org.elasticsearch.core.CheckedFunction;
|
||||||
|
@ -89,10 +91,12 @@ import javax.security.auth.x500.X500Principal;
|
||||||
import static org.elasticsearch.test.FileMatchers.isDirectory;
|
import static org.elasticsearch.test.FileMatchers.isDirectory;
|
||||||
import static org.elasticsearch.test.FileMatchers.isRegularFile;
|
import static org.elasticsearch.test.FileMatchers.isRegularFile;
|
||||||
import static org.elasticsearch.test.FileMatchers.pathExists;
|
import static org.elasticsearch.test.FileMatchers.pathExists;
|
||||||
|
import static org.elasticsearch.xpack.security.cli.CertGenUtilsTests.assertExpectedKeyUsage;
|
||||||
import static org.elasticsearch.xpack.security.cli.HttpCertificateCommand.guessFileType;
|
import static org.elasticsearch.xpack.security.cli.HttpCertificateCommand.guessFileType;
|
||||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.arrayWithSize;
|
import static org.hamcrest.Matchers.arrayWithSize;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.in;
|
import static org.hamcrest.Matchers.in;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
@ -369,21 +373,25 @@ public class HttpCertificateCommandTests extends ESTestCase {
|
||||||
final String caDN;
|
final String caDN;
|
||||||
final int caYears;
|
final int caYears;
|
||||||
final int caKeySize;
|
final int caKeySize;
|
||||||
|
final List<String> caKeyUsage;
|
||||||
// randomise whether to change CA defaults.
|
// randomise whether to change CA defaults.
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
terminal.addTextInput("y"); // Change defaults
|
terminal.addTextInput("y"); // Change defaults
|
||||||
caDN = "CN=" + randomAlphaOfLengthBetween(3, 8);
|
caDN = "CN=" + randomAlphaOfLengthBetween(3, 8);
|
||||||
caYears = randomIntBetween(1, 3);
|
caYears = randomIntBetween(1, 3);
|
||||||
caKeySize = randomFrom(2048, 3072, 4096);
|
caKeySize = randomFrom(2048, 3072, 4096);
|
||||||
|
caKeyUsage = randomSubsetOf(CertGenUtils.KEY_USAGE_MAPPINGS.keySet());
|
||||||
terminal.addTextInput(caDN);
|
terminal.addTextInput(caDN);
|
||||||
terminal.addTextInput(caYears + "y");
|
terminal.addTextInput(caYears + "y");
|
||||||
terminal.addTextInput(Integer.toString(caKeySize));
|
terminal.addTextInput(Integer.toString(caKeySize));
|
||||||
|
terminal.addTextInput(Strings.collectionToCommaDelimitedString(caKeyUsage));
|
||||||
terminal.addTextInput("n"); // Don't change values
|
terminal.addTextInput("n"); // Don't change values
|
||||||
} else {
|
} else {
|
||||||
terminal.addTextInput(randomBoolean() ? "n" : ""); // Don't change defaults
|
terminal.addTextInput(randomBoolean() ? "n" : ""); // Don't change defaults
|
||||||
caDN = HttpCertificateCommand.DEFAULT_CA_NAME.toString();
|
caDN = HttpCertificateCommand.DEFAULT_CA_NAME.toString();
|
||||||
caYears = HttpCertificateCommand.DEFAULT_CA_VALIDITY.getYears();
|
caYears = HttpCertificateCommand.DEFAULT_CA_VALIDITY.getYears();
|
||||||
caKeySize = HttpCertificateCommand.DEFAULT_CA_KEY_SIZE;
|
caKeySize = HttpCertificateCommand.DEFAULT_CA_KEY_SIZE;
|
||||||
|
caKeyUsage = HttpCertificateCommand.DEFAULT_CA_KEY_USAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String caPassword = randomPassword(randomBoolean());
|
final String caPassword = randomPassword(randomBoolean());
|
||||||
|
@ -463,6 +471,7 @@ public class HttpCertificateCommandTests extends ESTestCase {
|
||||||
verifyCertificate(caCertKey.v1(), caDN.replaceFirst("CN=", ""), caYears, List.of(), List.of());
|
verifyCertificate(caCertKey.v1(), caDN.replaceFirst("CN=", ""), caYears, List.of(), List.of());
|
||||||
assertThat(getRSAKeySize(caCertKey.v1().getPublicKey()), is(caKeySize));
|
assertThat(getRSAKeySize(caCertKey.v1().getPublicKey()), is(caKeySize));
|
||||||
assertThat(getRSAKeySize(caCertKey.v2()), is(caKeySize));
|
assertThat(getRSAKeySize(caCertKey.v2()), is(caKeySize));
|
||||||
|
assertExpectedKeyUsage(caCertKey.v1(), caKeyUsage);
|
||||||
|
|
||||||
assertThat(zipRoot.resolve("elasticsearch"), isDirectory());
|
assertThat(zipRoot.resolve("elasticsearch"), isDirectory());
|
||||||
|
|
||||||
|
@ -486,6 +495,7 @@ public class HttpCertificateCommandTests extends ESTestCase {
|
||||||
verifyChain(certAndKey.v1(), caCertKey.v1());
|
verifyChain(certAndKey.v1(), caCertKey.v1());
|
||||||
assertThat(getRSAKeySize(certAndKey.v1().getPublicKey()), is(HttpCertificateCommand.DEFAULT_CERT_KEY_SIZE));
|
assertThat(getRSAKeySize(certAndKey.v1().getPublicKey()), is(HttpCertificateCommand.DEFAULT_CERT_KEY_SIZE));
|
||||||
assertThat(getRSAKeySize(certAndKey.v2()), is(HttpCertificateCommand.DEFAULT_CERT_KEY_SIZE));
|
assertThat(getRSAKeySize(certAndKey.v2()), is(HttpCertificateCommand.DEFAULT_CERT_KEY_SIZE));
|
||||||
|
assertExpectedKeyUsage(certAndKey.v1(), HttpCertificateCommand.DEFAULT_CERT_KEY_USAGE);
|
||||||
|
|
||||||
// Verify the README
|
// Verify the README
|
||||||
assertThat(readme, containsString(p12Path.getFileName().toString()));
|
assertThat(readme, containsString(p12Path.getFileName().toString()));
|
||||||
|
@ -692,7 +702,10 @@ public class HttpCertificateCommandTests extends ESTestCase {
|
||||||
// We register 1 extension with the subject alternative names and extended key usage
|
// We register 1 extension with the subject alternative names and extended key usage
|
||||||
final Extensions extensions = Extensions.getInstance(extensionAttributes[0].getAttributeValues()[0]);
|
final Extensions extensions = Extensions.getInstance(extensionAttributes[0].getAttributeValues()[0]);
|
||||||
assertThat(extensions, notNullValue());
|
assertThat(extensions, notNullValue());
|
||||||
assertThat(extensions.getExtensionOIDs(), arrayWithSize(2));
|
assertThat(
|
||||||
|
extensions.getExtensionOIDs(),
|
||||||
|
arrayContainingInAnyOrder(Extension.subjectAlternativeName, Extension.keyUsage, Extension.extendedKeyUsage)
|
||||||
|
);
|
||||||
|
|
||||||
final GeneralNames names = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
|
final GeneralNames names = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
|
||||||
assertThat(names.getNames(), arrayWithSize(hostNames.size() + ipAddresses.size()));
|
assertThat(names.getNames(), arrayWithSize(hostNames.size() + ipAddresses.size()));
|
||||||
|
@ -709,6 +722,9 @@ public class HttpCertificateCommandTests extends ESTestCase {
|
||||||
|
|
||||||
ExtendedKeyUsage extendedKeyUsage = ExtendedKeyUsage.fromExtensions(extensions);
|
ExtendedKeyUsage extendedKeyUsage = ExtendedKeyUsage.fromExtensions(extensions);
|
||||||
assertThat(extendedKeyUsage.getUsages(), arrayContainingInAnyOrder(KeyPurposeId.id_kp_serverAuth));
|
assertThat(extendedKeyUsage.getUsages(), arrayContainingInAnyOrder(KeyPurposeId.id_kp_serverAuth));
|
||||||
|
|
||||||
|
KeyUsage keyUsage = KeyUsage.fromExtensions(extensions);
|
||||||
|
assertThat(keyUsage, is(equalTo(new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyCertificate(
|
private void verifyCertificate(
|
||||||
|
|
Loading…
Reference in New Issue