2020import org .bouncycastle .asn1 .x509 .ExtensionsGenerator ;
2121import org .bouncycastle .asn1 .x509 .GeneralName ;
2222import org .bouncycastle .asn1 .x509 .GeneralNames ;
23+ import org .bouncycastle .asn1 .x509 .KeyUsage ;
2324import org .bouncycastle .asn1 .x509 .Time ;
2425import org .bouncycastle .cert .CertIOException ;
2526import org .bouncycastle .cert .X509CertificateHolder ;
5354import java .sql .Date ;
5455import java .time .ZoneOffset ;
5556import java .time .ZonedDateTime ;
57+ import java .util .Collection ;
58+ import java .util .Collections ;
5659import java .util .HashSet ;
5760import java .util .Locale ;
61+ import java .util .Map ;
5862import java .util .Objects ;
5963import java .util .Set ;
64+ import java .util .TreeMap ;
6065
6166import javax .net .ssl .X509ExtendedKeyManager ;
6267import javax .net .ssl .X509ExtendedTrustManager ;
@@ -73,14 +78,33 @@ public class CertGenUtils {
7378 private static final int SERIAL_BIT_LENGTH = 20 * 8 ;
7479 private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider ();
7580
81+ /**
82+ * The mapping of key usage names to their corresponding integer values as defined in {@code KeyUsage} class.
83+ */
84+ public static final Map <String , Integer > KEY_USAGE_MAPPINGS = Collections .unmodifiableMap (
85+ new TreeMap <>(
86+ Map .ofEntries (
87+ Map .entry ("digitalSignature" , KeyUsage .digitalSignature ),
88+ Map .entry ("nonRepudiation" , KeyUsage .nonRepudiation ),
89+ Map .entry ("keyEncipherment" , KeyUsage .keyEncipherment ),
90+ Map .entry ("dataEncipherment" , KeyUsage .dataEncipherment ),
91+ Map .entry ("keyAgreement" , KeyUsage .keyAgreement ),
92+ Map .entry ("keyCertSign" , KeyUsage .keyCertSign ),
93+ Map .entry ("cRLSign" , KeyUsage .cRLSign ),
94+ Map .entry ("encipherOnly" , KeyUsage .encipherOnly ),
95+ Map .entry ("decipherOnly" , KeyUsage .decipherOnly )
96+ )
97+ )
98+ );
99+
76100 private CertGenUtils () {}
77101
78102 /**
79103 * Generates a CA certificate
80104 */
81- public static X509Certificate generateCACertificate (X500Principal x500Principal , KeyPair keyPair , int days )
105+ public static X509Certificate generateCACertificate (X500Principal x500Principal , KeyPair keyPair , int days , KeyUsage keyUsage )
82106 throws OperatorCreationException , CertificateException , CertIOException , NoSuchAlgorithmException {
83- return generateSignedCertificate (x500Principal , null , keyPair , null , null , true , days , null );
107+ return generateSignedCertificate (x500Principal , null , keyPair , null , null , true , days , null , keyUsage , Set . of () );
84108 }
85109
86110 /**
@@ -107,7 +131,7 @@ public static X509Certificate generateSignedCertificate(
107131 PrivateKey caPrivKey ,
108132 int days
109133 ) throws OperatorCreationException , CertificateException , CertIOException , NoSuchAlgorithmException {
110- return generateSignedCertificate (principal , subjectAltNames , keyPair , caCert , caPrivKey , false , days , null );
134+ return generateSignedCertificate (principal , subjectAltNames , keyPair , caCert , caPrivKey , false , days , null , null , Set . of () );
111135 }
112136
113137 /**
@@ -123,54 +147,14 @@ public static X509Certificate generateSignedCertificate(
123147 * certificate
124148 * @param caPrivKey the CA private key. If {@code null}, this results in a self signed
125149 * certificate
126- * @param days no of days certificate will be valid from now
127- * @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
128- * empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
129- * @return a signed {@link X509Certificate}
130- */
131- public static X509Certificate generateSignedCertificate (
132- X500Principal principal ,
133- GeneralNames subjectAltNames ,
134- KeyPair keyPair ,
135- X509Certificate caCert ,
136- PrivateKey caPrivKey ,
137- int days ,
138- String signatureAlgorithm
139- ) throws OperatorCreationException , CertificateException , CertIOException , NoSuchAlgorithmException {
140- return generateSignedCertificate (principal , subjectAltNames , keyPair , caCert , caPrivKey , false , days , signatureAlgorithm );
141- }
142-
143- /**
144- * Generates a signed certificate
145- *
146- * @param principal the principal of the certificate; commonly referred to as the
147- * distinguished name (DN)
148- * @param subjectAltNames the subject alternative names that should be added to the
149- * certificate as an X509v3 extension. May be {@code null}
150- * @param keyPair the key pair that will be associated with the certificate
151- * @param caCert the CA certificate. If {@code null}, this results in a self signed
152- * certificate
153- * @param caPrivKey the CA private key. If {@code null}, this results in a self signed
154- * certificate
155150 * @param isCa whether or not the generated certificate is a CA
156151 * @param days no of days certificate will be valid from now
157152 * @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or
158153 * empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)}
154+ * @param keyUsage the key usage that should be added to the certificate as a X509v3 extension (can be {@code null})
155+ * @param extendedKeyUsages the extended key usages that should be added to the certificate as a X509v3 extension (can be empty)
159156 * @return a signed {@link X509Certificate}
160157 */
161- public static X509Certificate generateSignedCertificate (
162- X500Principal principal ,
163- GeneralNames subjectAltNames ,
164- KeyPair keyPair ,
165- X509Certificate caCert ,
166- PrivateKey caPrivKey ,
167- boolean isCa ,
168- int days ,
169- String signatureAlgorithm
170- ) throws NoSuchAlgorithmException , CertificateException , CertIOException , OperatorCreationException {
171- return generateSignedCertificate (principal , subjectAltNames , keyPair , caCert , caPrivKey , isCa , days , signatureAlgorithm , Set .of ());
172- }
173-
174158 public static X509Certificate generateSignedCertificate (
175159 X500Principal principal ,
176160 GeneralNames subjectAltNames ,
@@ -180,6 +164,7 @@ public static X509Certificate generateSignedCertificate(
180164 boolean isCa ,
181165 int days ,
182166 String signatureAlgorithm ,
167+ KeyUsage keyUsage ,
183168 Set <ExtendedKeyUsage > extendedKeyUsages
184169 ) throws NoSuchAlgorithmException , CertificateException , CertIOException , OperatorCreationException {
185170 Objects .requireNonNull (keyPair , "Key-Pair must not be null" );
@@ -198,6 +183,7 @@ public static X509Certificate generateSignedCertificate(
198183 notBefore ,
199184 notAfter ,
200185 signatureAlgorithm ,
186+ keyUsage ,
201187 extendedKeyUsages
202188 );
203189 }
@@ -223,6 +209,7 @@ public static X509Certificate generateSignedCertificate(
223209 notBefore ,
224210 notAfter ,
225211 signatureAlgorithm ,
212+ null ,
226213 Set .of ()
227214 );
228215 }
@@ -237,6 +224,7 @@ public static X509Certificate generateSignedCertificate(
237224 ZonedDateTime notBefore ,
238225 ZonedDateTime notAfter ,
239226 String signatureAlgorithm ,
227+ KeyUsage keyUsage ,
240228 Set <ExtendedKeyUsage > extendedKeyUsages
241229 ) throws NoSuchAlgorithmException , CertIOException , OperatorCreationException , CertificateException {
242230 final BigInteger serial = CertGenUtils .getSerial ();
@@ -272,6 +260,11 @@ public static X509Certificate generateSignedCertificate(
272260 }
273261 builder .addExtension (Extension .basicConstraints , isCa , new BasicConstraints (isCa ));
274262
263+ if (keyUsage != null ) {
264+ // as per RFC 5280 (section 4.2.1.3), if the key usage is present, then it SHOULD be marked as critical.
265+ final boolean isCritical = true ;
266+ builder .addExtension (Extension .keyUsage , isCritical , keyUsage );
267+ }
275268 if (extendedKeyUsages != null ) {
276269 for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages ) {
277270 builder .addExtension (Extension .extendedKeyUsage , false , extendedKeyUsage );
@@ -318,7 +311,7 @@ private static String getDefaultSignatureAlgorithm(PrivateKey key) {
318311 */
319312 static PKCS10CertificationRequest generateCSR (KeyPair keyPair , X500Principal principal , GeneralNames sanList ) throws IOException ,
320313 OperatorCreationException {
321- return generateCSR (keyPair , principal , sanList , Set .of ());
314+ return generateCSR (keyPair , principal , sanList , null , Set .of ());
322315 }
323316
324317 /**
@@ -335,6 +328,7 @@ static PKCS10CertificationRequest generateCSR(
335328 KeyPair keyPair ,
336329 X500Principal principal ,
337330 GeneralNames sanList ,
331+ KeyUsage keyUsage ,
338332 Set <ExtendedKeyUsage > extendedKeyUsages
339333 ) throws IOException , OperatorCreationException {
340334 Objects .requireNonNull (keyPair , "Key-Pair must not be null" );
@@ -347,7 +341,9 @@ static PKCS10CertificationRequest generateCSR(
347341 if (sanList != null ) {
348342 extGen .addExtension (Extension .subjectAlternativeName , false , sanList );
349343 }
350-
344+ if (keyUsage != null ) {
345+ extGen .addExtension (Extension .keyUsage , true , keyUsage );
346+ }
351347 for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages ) {
352348 extGen .addExtension (Extension .extendedKeyUsage , false , extendedKeyUsage );
353349 }
@@ -430,4 +426,31 @@ public static GeneralName createCommonName(String cn) {
430426 public static String buildDnFromDomain (String domain ) {
431427 return "DC=" + domain .replace ("." , ",DC=" );
432428 }
429+
430+ public static KeyUsage buildKeyUsage (Collection <String > keyUsages ) {
431+ if (keyUsages == null || keyUsages .isEmpty ()) {
432+ return null ;
433+ }
434+
435+ int usageBits = 0 ;
436+ for (String keyUsageName : keyUsages ) {
437+ Integer keyUsageValue = findKeyUsageByName (keyUsageName );
438+ if (keyUsageValue == null ) {
439+ throw new IllegalArgumentException ("Unknown keyUsage: " + keyUsageName );
440+ }
441+ usageBits |= keyUsageValue ;
442+ }
443+ return new KeyUsage (usageBits );
444+ }
445+
446+ public static boolean isValidKeyUsage (String keyUsage ) {
447+ return findKeyUsageByName (keyUsage ) != null ;
448+ }
449+
450+ private static Integer findKeyUsageByName (String keyUsageName ) {
451+ if (keyUsageName == null ) {
452+ return null ;
453+ }
454+ return KEY_USAGE_MAPPINGS .get (keyUsageName .trim ());
455+ }
433456}
0 commit comments