-
Notifications
You must be signed in to change notification settings - Fork 25.5k
Set keyUsage
for generated HTTP certificates and self-signed CA
#126376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
slobodanadamovic
merged 23 commits into
elastic:main
from
slobodanadamovic:sa-include-key-usage-in-http-certs
Apr 8, 2025
Merged
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
0f6dffd
Set `keyUsage` for generated HTTP certificates and self-signed CA
slobodanadamovic 2ed9991
Update docs/changelog/126376.yaml
slobodanadamovic 11e96ee
Merge branch 'main' into sa-include-key-usage-in-http-certs
slobodanadamovic 2a77f86
make default HTTP `keyUsage` overridable
slobodanadamovic 8c7fa75
[CI] Auto commit changes from spotless
09f698e
respect custom keyUsage
slobodanadamovic c0f66f3
fix terminal messages and ignore leading and trailing whitespaces
slobodanadamovic 814f5db
nit: error message
slobodanadamovic 411f5cf
add extra line after error message
slobodanadamovic e1ef610
more test coverage
slobodanadamovic d8c8d30
nit: error message
slobodanadamovic b05a367
add `ca-keyusage` option
slobodanadamovic adcf14c
cleanup, test and update docs
slobodanadamovic 5f160cf
update ca-keyusage description
slobodanadamovic 6d59a54
Merge branch 'main' into sa-include-key-usage-in-http-certs
slobodanadamovic 992b8ee
remove randomization to avoid raising ValidatorException:
slobodanadamovic 10d682d
Merge branch 'sa-include-key-usage-in-http-certs' of github.com:slobo…
slobodanadamovic 41b982f
move bit map to test class
slobodanadamovic fa68b1a
map of entires
slobodanadamovic 5fdda88
rename `ca-keyusage` to `keyusage` and make it applicable only to `ca`
slobodanadamovic e03f096
remove obsolete dependencies to unapplicable ca options
slobodanadamovic 06c04c0
make it private
slobodanadamovic f238cd0
Merge branch 'main' into sa-include-key-usage-in-http-certs
slobodanadamovic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pr: 126376 | ||
summary: Set `keyUsage` for generated HTTP certificates and self-signed CA | ||
area: TLS | ||
type: bug | ||
issues: | ||
- 117769 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
import org.bouncycastle.asn1.x509.ExtensionsGenerator; | ||
import org.bouncycastle.asn1.x509.GeneralName; | ||
import org.bouncycastle.asn1.x509.GeneralNames; | ||
import org.bouncycastle.asn1.x509.KeyUsage; | ||
import org.bouncycastle.asn1.x509.Time; | ||
import org.bouncycastle.cert.CertIOException; | ||
import org.bouncycastle.cert.X509CertificateHolder; | ||
|
@@ -53,8 +54,10 @@ | |
import java.sql.Date; | ||
import java.time.ZoneOffset; | ||
import java.time.ZonedDateTime; | ||
import java.util.Collection; | ||
import java.util.HashSet; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
|
@@ -73,14 +76,74 @@ public class CertGenUtils { | |
private static final int SERIAL_BIT_LENGTH = 20 * 8; | ||
private static final BouncyCastleProvider BC_PROV = new BouncyCastleProvider(); | ||
|
||
/** | ||
* The mapping of key usage names to their corresponding integer values as defined in {@code KeyUsage} class. | ||
*/ | ||
public static final Map<String, Integer> KEY_USAGE_MAPPINGS = Map.of( | ||
"digitalSignature", | ||
KeyUsage.digitalSignature, | ||
"nonRepudiation", | ||
KeyUsage.nonRepudiation, | ||
"keyEncipherment", | ||
KeyUsage.keyEncipherment, | ||
"dataEncipherment", | ||
KeyUsage.dataEncipherment, | ||
"keyAgreement", | ||
KeyUsage.keyAgreement, | ||
"keyCertSign", | ||
KeyUsage.keyCertSign, | ||
"cRLSign", | ||
KeyUsage.cRLSign, | ||
"encipherOnly", | ||
KeyUsage.encipherOnly, | ||
"decipherOnly", | ||
KeyUsage.decipherOnly | ||
); | ||
|
||
/** | ||
* 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> | ||
*/ | ||
public static final Map<String, Integer> KEY_USAGE_BITS = Map.of( | ||
"digitalSignature", | ||
0, | ||
"nonRepudiation", | ||
1, | ||
"keyEncipherment", | ||
2, | ||
"dataEncipherment", | ||
3, | ||
"keyAgreement", | ||
4, | ||
"keyCertSign", | ||
5, | ||
"cRLSign", | ||
6, | ||
"encipherOnly", | ||
7, | ||
"decipherOnly", | ||
8 | ||
); | ||
|
||
private CertGenUtils() {} | ||
|
||
/** | ||
* Generates a CA certificate | ||
*/ | ||
public static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair, int days) | ||
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, null, Set.of()); | ||
} | ||
|
||
/** | ||
|
@@ -107,7 +170,7 @@ public static X509Certificate generateSignedCertificate( | |
PrivateKey caPrivKey, | ||
int days | ||
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException { | ||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null); | ||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, null, null, Set.of()); | ||
} | ||
|
||
/** | ||
|
@@ -123,54 +186,14 @@ public static X509Certificate generateSignedCertificate( | |
* certificate | ||
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed | ||
* certificate | ||
* @param days no of days certificate will be valid from now | ||
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or | ||
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)} | ||
* @return a signed {@link X509Certificate} | ||
*/ | ||
public static X509Certificate generateSignedCertificate( | ||
X500Principal principal, | ||
GeneralNames subjectAltNames, | ||
KeyPair keyPair, | ||
X509Certificate caCert, | ||
PrivateKey caPrivKey, | ||
int days, | ||
String signatureAlgorithm | ||
) throws OperatorCreationException, CertificateException, CertIOException, NoSuchAlgorithmException { | ||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false, days, signatureAlgorithm); | ||
} | ||
|
||
/** | ||
* Generates a signed certificate | ||
* | ||
* @param principal the principal of the certificate; commonly referred to as the | ||
* distinguished name (DN) | ||
* @param subjectAltNames the subject alternative names that should be added to the | ||
* certificate as an X509v3 extension. May be {@code null} | ||
* @param keyPair the key pair that will be associated with the certificate | ||
* @param caCert the CA certificate. If {@code null}, this results in a self signed | ||
* certificate | ||
* @param caPrivKey the CA private key. If {@code null}, this results in a self signed | ||
* certificate | ||
* @param isCa whether or not the generated certificate is a CA | ||
* @param days no of days certificate will be valid from now | ||
* @param signatureAlgorithm algorithm used for signing certificate. If {@code null} or | ||
* empty, then use default algorithm {@link CertGenUtils#getDefaultSignatureAlgorithm(PrivateKey)} | ||
* @param keyUsage the key usage that should be added to the certificate as a X509v3 extension (can be {@code null}) | ||
* @param extendedKeyUsages the extended key usages that should be added to the certificate as a X509v3 extension (can be empty) | ||
* @return a signed {@link X509Certificate} | ||
*/ | ||
public static X509Certificate generateSignedCertificate( | ||
X500Principal principal, | ||
GeneralNames subjectAltNames, | ||
KeyPair keyPair, | ||
X509Certificate caCert, | ||
PrivateKey caPrivKey, | ||
boolean isCa, | ||
int days, | ||
String signatureAlgorithm | ||
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException { | ||
return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, isCa, days, signatureAlgorithm, Set.of()); | ||
} | ||
|
||
public static X509Certificate generateSignedCertificate( | ||
X500Principal principal, | ||
GeneralNames subjectAltNames, | ||
|
@@ -180,6 +203,7 @@ public static X509Certificate generateSignedCertificate( | |
boolean isCa, | ||
int days, | ||
String signatureAlgorithm, | ||
KeyUsage keyUsage, | ||
Set<ExtendedKeyUsage> extendedKeyUsages | ||
) throws NoSuchAlgorithmException, CertificateException, CertIOException, OperatorCreationException { | ||
Objects.requireNonNull(keyPair, "Key-Pair must not be null"); | ||
|
@@ -198,6 +222,7 @@ public static X509Certificate generateSignedCertificate( | |
notBefore, | ||
notAfter, | ||
signatureAlgorithm, | ||
keyUsage, | ||
extendedKeyUsages | ||
); | ||
} | ||
|
@@ -223,6 +248,7 @@ public static X509Certificate generateSignedCertificate( | |
notBefore, | ||
notAfter, | ||
signatureAlgorithm, | ||
null, | ||
Set.of() | ||
); | ||
} | ||
|
@@ -237,6 +263,7 @@ public static X509Certificate generateSignedCertificate( | |
ZonedDateTime notBefore, | ||
ZonedDateTime notAfter, | ||
String signatureAlgorithm, | ||
KeyUsage keyUsage, | ||
Set<ExtendedKeyUsage> extendedKeyUsages | ||
) throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException { | ||
final BigInteger serial = CertGenUtils.getSerial(); | ||
|
@@ -272,6 +299,11 @@ public static X509Certificate generateSignedCertificate( | |
} | ||
builder.addExtension(Extension.basicConstraints, isCa, new BasicConstraints(isCa)); | ||
|
||
if (keyUsage != null) { | ||
// as per RFC 5280 (section 4.2.1.3), if the key usage is present, then it SHOULD be marked as critical. | ||
final boolean isCritical = true; | ||
builder.addExtension(Extension.keyUsage, isCritical, keyUsage); | ||
} | ||
if (extendedKeyUsages != null) { | ||
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) { | ||
builder.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage); | ||
|
@@ -318,7 +350,7 @@ private static String getDefaultSignatureAlgorithm(PrivateKey key) { | |
*/ | ||
static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws IOException, | ||
OperatorCreationException { | ||
return generateCSR(keyPair, principal, sanList, Set.of()); | ||
return generateCSR(keyPair, principal, sanList, null, Set.of()); | ||
} | ||
|
||
/** | ||
|
@@ -335,6 +367,7 @@ static PKCS10CertificationRequest generateCSR( | |
KeyPair keyPair, | ||
X500Principal principal, | ||
GeneralNames sanList, | ||
KeyUsage keyUsage, | ||
Set<ExtendedKeyUsage> extendedKeyUsages | ||
) throws IOException, OperatorCreationException { | ||
Objects.requireNonNull(keyPair, "Key-Pair must not be null"); | ||
|
@@ -347,7 +380,9 @@ static PKCS10CertificationRequest generateCSR( | |
if (sanList != null) { | ||
extGen.addExtension(Extension.subjectAlternativeName, false, sanList); | ||
} | ||
|
||
if (keyUsage != null) { | ||
extGen.addExtension(Extension.keyUsage, true, keyUsage); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's debatable if CSR should include |
||
} | ||
for (ExtendedKeyUsage extendedKeyUsage : extendedKeyUsages) { | ||
extGen.addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage); | ||
} | ||
|
@@ -430,4 +465,24 @@ public static GeneralName createCommonName(String cn) { | |
public static String buildDnFromDomain(String domain) { | ||
return "DC=" + domain.replace(".", ",DC="); | ||
} | ||
|
||
public static KeyUsage buildKeyUsage(Collection<String> keyUsages) { | ||
if (keyUsages == null || keyUsages.isEmpty()) { | ||
return null; | ||
} | ||
|
||
int usageBits = 0; | ||
for (String keyUsage : keyUsages) { | ||
Integer keyUsageValue = KEY_USAGE_MAPPINGS.get(keyUsage); | ||
if (keyUsageValue == null) { | ||
throw new IllegalArgumentException("Unknown keyUsage: " + keyUsage); | ||
} | ||
usageBits |= keyUsageValue; | ||
} | ||
return new KeyUsage(usageBits); | ||
} | ||
|
||
public static boolean isValidKeyUsage(String keyUsage) { | ||
return KEY_USAGE_MAPPINGS.containsKey(keyUsage); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.