1414import org .bouncycastle .asn1 .x509 .ExtendedKeyUsage ;
1515import org .bouncycastle .asn1 .x509 .GeneralNames ;
1616import org .bouncycastle .asn1 .x509 .KeyPurposeId ;
17- import org .bouncycastle .asn1 .x509 .KeyUsage ;
1817import org .bouncycastle .cert .CertIOException ;
1918import org .bouncycastle .openssl .jcajce .JcaMiscPEMGenerator ;
2019import org .bouncycastle .openssl .jcajce .JcaPEMWriter ;
6968import java .time .format .DateTimeParseException ;
7069import java .util .ArrayList ;
7170import java .util .Arrays ;
71+ import java .util .Collection ;
7272import java .util .List ;
7373import java .util .Locale ;
7474import java .util .Map ;
8181
8282import javax .security .auth .x500 .X500Principal ;
8383
84+ import static org .elasticsearch .xpack .security .cli .CertGenUtils .buildKeyUsage ;
8485import static org .elasticsearch .xpack .security .cli .CertGenUtils .generateSignedCertificate ;
8586
8687/**
@@ -96,7 +97,8 @@ class HttpCertificateCommand extends EnvironmentAwareCommand {
9697 static final X500Principal DEFAULT_CA_NAME = new X500Principal ("CN=Elasticsearch HTTP CA" );
9798 static final int DEFAULT_CA_KEY_SIZE = DEFAULT_CERT_KEY_SIZE ;
9899 static final Period DEFAULT_CA_VALIDITY = DEFAULT_CERT_VALIDITY ;
99-
100+ static final List <String > DEFAULT_CA_KEY_USAGE = List .of ("keyCertSign" , "cRLSign" );
101+ static final List <String > DEFAULT_CERT_KEY_USAGE = List .of ("digitalSignature" , "keyEncipherment" );
100102 private static final String ES_README_CSR = "es-readme-csr.txt" ;
101103 private static final String ES_YML_CSR = "es-sample-csr.yml" ;
102104 private static final String ES_README_P12 = "es-readme-p12.txt" ;
@@ -134,14 +136,24 @@ private class CertOptions {
134136 final List <String > dnsNames ;
135137 final List <String > ipNames ;
136138 final int keySize ;
139+ final List <String > keyUsage ;
137140 final Period validity ;
138141
139- private CertOptions (String name , X500Principal subject , List <String > dnsNames , List <String > ipNames , int keySize , Period validity ) {
142+ private CertOptions (
143+ String name ,
144+ X500Principal subject ,
145+ List <String > dnsNames ,
146+ List <String > ipNames ,
147+ int keySize ,
148+ List <String > keyUsage ,
149+ Period validity
150+ ) {
140151 this .name = name ;
141152 this .subject = subject ;
142153 this .dnsNames = dnsNames ;
143154 this .ipNames = ipNames ;
144155 this .keySize = keySize ;
156+ this .keyUsage = keyUsage ;
145157 this .validity = validity ;
146158 }
147159 }
@@ -195,6 +207,7 @@ public void execute(Terminal terminal, OptionSet options, Environment env, Proce
195207 terminal .println (Terminal .Verbosity .VERBOSE , "\t DNS Names: " + Strings .collectionToCommaDelimitedString (cert .dnsNames ));
196208 terminal .println (Terminal .Verbosity .VERBOSE , "\t IP Names: " + Strings .collectionToCommaDelimitedString (cert .ipNames ));
197209 terminal .println (Terminal .Verbosity .VERBOSE , "\t Key Size: " + cert .keySize );
210+ terminal .println (Terminal .Verbosity .VERBOSE , "\t Key Usage: " + Strings .collectionToCommaDelimitedString (cert .keyUsage ));
198211 terminal .println (Terminal .Verbosity .VERBOSE , "\t Validity: " + toString (cert .validity ));
199212 certificates .add (cert );
200213
@@ -340,7 +353,7 @@ private void writeCertificateAndKeyDetails(
340353 keyPair ,
341354 cert .subject ,
342355 sanList ,
343- new KeyUsage ( KeyUsage . digitalSignature | KeyUsage . keyEncipherment ),
356+ buildKeyUsage ( DEFAULT_CERT_KEY_USAGE ),
344357 Set .of (new ExtendedKeyUsage (KeyPurposeId .id_kp_serverAuth ))
345358 );
346359 final String csrFile = "http-" + cert .name + ".csr" ;
@@ -374,7 +387,7 @@ private void writeCertificateAndKeyDetails(
374387 notBefore ,
375388 notAfter ,
376389 null ,
377- new KeyUsage ( KeyUsage . digitalSignature | KeyUsage . keyEncipherment ),
390+ buildKeyUsage ( DEFAULT_CERT_KEY_USAGE ),
378391 Set .of (new ExtendedKeyUsage (KeyPurposeId .id_kp_serverAuth ))
379392 );
380393
@@ -695,10 +708,12 @@ private CertOptions getCertificateConfiguration(
695708 }
696709 X500Principal dn = buildDistinguishedName (certName );
697710 int keySize = DEFAULT_CERT_KEY_SIZE ;
711+ List <String > keyUsage = DEFAULT_CERT_KEY_USAGE ;
698712 while (true ) {
699713 terminal .println (Terminal .Verbosity .SILENT , "Key Name: " + certName );
700714 terminal .println (Terminal .Verbosity .SILENT , "Subject DN: " + dn );
701715 terminal .println (Terminal .Verbosity .SILENT , "Key Size: " + keySize );
716+ terminal .println (Terminal .Verbosity .SILENT , "Key Usage: " + Strings .collectionToCommaDelimitedString (keyUsage ));
702717 terminal .println (Terminal .Verbosity .SILENT , "" );
703718 if (terminal .promptYesNo ("Do you wish to change any of these options?" , false ) == false ) {
704719 break ;
@@ -739,9 +754,21 @@ private CertOptions getCertificateConfiguration(
739754
740755 keySize = readKeySize (terminal , keySize );
741756 terminal .println ("" );
757+
758+ printHeader ("What key usage should your certificate have?" , terminal );
759+ terminal .println ("The key usage extension defines the purpose of the key contained in the certificate." );
760+ terminal .println (
761+ "The usage restriction might be employed when a key, that could be used for more than one operation, is to be restricted."
762+ );
763+ terminal .println ("You may enter the key usage as a comma-delimited list of following values: " );
764+ terminal .println (" - " + CertGenUtils .KEY_USAGE_MAPPINGS .keySet ().stream ().sorted ());
765+ terminal .println ("" );
766+
767+ keyUsage = readKeyUsage (terminal , keyUsage );
768+ terminal .println ("" );
742769 }
743770
744- return new CertOptions (certName , dn , dnsNames , ipNames , keySize , validity );
771+ return new CertOptions (certName , dn , dnsNames , ipNames , keySize , keyUsage , validity );
745772 }
746773
747774 private static String validateHostname (String name ) {
@@ -862,10 +889,12 @@ private CertificateTool.CAInfo createNewCA(Terminal terminal) {
862889 X500Principal dn = DEFAULT_CA_NAME ;
863890 Period validity = DEFAULT_CA_VALIDITY ;
864891 int keySize = DEFAULT_CA_KEY_SIZE ;
892+ List <String > keyUsage = DEFAULT_CA_KEY_USAGE ;
865893 while (true ) {
866894 terminal .println (Terminal .Verbosity .SILENT , "Subject DN: " + dn );
867895 terminal .println (Terminal .Verbosity .SILENT , "Validity: " + toString (validity ));
868896 terminal .println (Terminal .Verbosity .SILENT , "Key Size: " + keySize );
897+ terminal .println (Terminal .Verbosity .SILENT , "Key Usage: " + Strings .collectionToCommaDelimitedString (keyUsage ));
869898 terminal .println (Terminal .Verbosity .SILENT , "" );
870899 if (terminal .promptYesNo ("Do you wish to change any of these options?" , false ) == false ) {
871900 break ;
@@ -907,13 +936,37 @@ private CertificateTool.CAInfo createNewCA(Terminal terminal) {
907936
908937 keySize = readKeySize (terminal , keySize );
909938 terminal .println ("" );
939+
940+ printHeader ("What key usage should your CA have?" , terminal );
941+ terminal .println ("The key usage extension defines the purpose of the key contained in the certificate." );
942+ terminal .println (
943+ "The usage restriction might be employed when a key, that could be used for more than one operation, is to be restricted."
944+ );
945+ terminal .println ("You may enter the key usage as a comma-delimited list of following values: " );
946+ terminal .println (" - " + CertGenUtils .KEY_USAGE_MAPPINGS .keySet ().stream ().sorted ());
947+ terminal .println ("" );
948+
949+ keyUsage = readKeyUsage (terminal , keyUsage );
950+ terminal .println ("" );
910951 }
911952
912953 try {
913954 final KeyPair keyPair = CertGenUtils .generateKeyPair (keySize );
914955 final ZonedDateTime notBefore = ZonedDateTime .now (ZoneOffset .UTC );
915956 final ZonedDateTime notAfter = notBefore .plus (validity );
916- X509Certificate caCert = generateSignedCertificate (dn , null , keyPair , null , null , true , notBefore , notAfter , null );
957+ X509Certificate caCert = generateSignedCertificate (
958+ dn ,
959+ null ,
960+ keyPair ,
961+ null ,
962+ null ,
963+ true ,
964+ notBefore ,
965+ notAfter ,
966+ null ,
967+ buildKeyUsage (keyUsage ),
968+ Set .of ()
969+ );
917970
918971 printHeader ("CA password" , terminal );
919972 terminal .println ("We recommend that you protect your CA private key with a strong password." );
@@ -982,6 +1035,28 @@ private static Integer readKeySize(Terminal terminal, int keySize) {
9821035 });
9831036 }
9841037
1038+ private static List <String > readKeyUsage (Terminal terminal , List <String > defaultKeyUsage ) {
1039+ return tryReadInput (terminal , "Key Usage" , defaultKeyUsage , input -> {
1040+ final String [] keyUsages = input .split ("," );
1041+ final List <String > resolvedKeyUsages = new ArrayList <>(keyUsages .length );
1042+ for (String keyUsage : keyUsages ) {
1043+ if (keyUsage .isEmpty ()) {
1044+ terminal .println ("Key usage cannot be empty" );
1045+ return null ;
1046+ }
1047+ if (CertGenUtils .isValidKeyUsage (keyUsage ) == false ) {
1048+ terminal .println ("Invalid key usage: " + keyUsage );
1049+ terminal .println (
1050+ "The key usage should be one of [" + CertGenUtils .KEY_USAGE_MAPPINGS .keySet ().stream ().sorted () + "] values"
1051+ );
1052+ return null ;
1053+ }
1054+ resolvedKeyUsages .add (keyUsage );
1055+ }
1056+ return resolvedKeyUsages ;
1057+ });
1058+ }
1059+
9851060 private static char [] readPassword (Terminal terminal , String prompt , boolean confirm ) {
9861061 while (true ) {
9871062 final char [] password = terminal .readSecret (prompt + " [<ENTER> for none]" );
@@ -1083,7 +1158,14 @@ private static boolean askExistingCertificateAuthority(Terminal terminal) {
10831158 }
10841159
10851160 private static <T > T tryReadInput (Terminal terminal , String prompt , T defaultValue , Function <String , T > parser ) {
1086- final String defaultStr = defaultValue instanceof Period ? toString ((Period ) defaultValue ) : String .valueOf (defaultValue );
1161+ final String defaultStr ;
1162+ if (defaultValue instanceof Period ) {
1163+ defaultStr = toString ((Period ) defaultValue );
1164+ } else if (defaultValue instanceof Collection <?> collection ) {
1165+ defaultStr = Strings .collectionToCommaDelimitedString (collection );
1166+ } else {
1167+ defaultStr = String .valueOf (defaultValue );
1168+ }
10871169 while (true ) {
10881170 final String input = terminal .readText (prompt + " [" + defaultStr + "] " );
10891171 if (Strings .isEmpty (input )) {
0 commit comments