Skip to content

Conversation

mpdonova
Copy link
Contributor

@mpdonova mpdonova commented Feb 19, 2025

This PR updates the CertificateBuilder with a new method that creates a new instance with common fields (subject name, public key, serial number, validity, and key uses) filled-in. One test, IPIdentities.java, is updated to show how the method can be used to create various certificates. I attached screenshots that compare the old hard-coded certificates (left) with the new generated certificates.

trusted-cert
server-cert
client-cert


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8325766: Extend CertificateBuilder to create trust and end entity certificates programmatically (Enhancement - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/23700/head:pull/23700
$ git checkout pull/23700

Update a local copy of the PR:
$ git checkout pull/23700
$ git pull https://git.openjdk.org/jdk.git pull/23700/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 23700

View PR using the GUI difftool:
$ git pr show -t 23700

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/23700.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Feb 19, 2025

👋 Welcome back mdonovan! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Feb 19, 2025

@mpdonova This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8325766: Extend CertificateBuilder to create trust and end entity certificates programmatically

Reviewed-by: mullan, abarashev

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 10 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the rfr Pull request is ready for review label Feb 19, 2025
@openjdk
Copy link

openjdk bot commented Feb 19, 2025

@mpdonova The following labels will be automatically applied to this pull request:

  • net
  • security

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@mlbridge
Copy link

mlbridge bot commented Feb 19, 2025

Webrevs

throws CertificateException, IOException {
SecureRandom random = new SecureRandom();

boolean [] keyUsage = new boolean[]{false, false, false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be easier to just use var keyUsage = new boolean[KeyUsage.values().length]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the array initialization.

@wangweij
Copy link
Contributor

The similarity between the certificate pairs is impressive! Just curious - why the change in issuer and owner names?

@mpdonova
Copy link
Contributor Author

The similarity between the certificate pairs is impressive! Just curious - why the change in issuer and owner names?

Looks like it's something between keytool and openssl x509. When i print the certificates with openssl, the issuer and owner names are in the opposite order from keytool.

@mpdonova
Copy link
Contributor Author

The similarity between the certificate pairs is impressive! Just curious - why the change in issuer and owner names?

After looking into this some more, I found that X500Name(String dname) is expecting the string to be in the same order as RFC 1779, 2253, or 4514. If you give the constructor a "backwards" string, it will store and print it backwards.

OpenSSL prints them backwards and I was using OpenSSL to print the hard-coded certificates and then just copying and pasting the strings.

I changed the hard-coded DN strings to follow the RFC order.

KeyUsage.DIGITAL_SIGNATURE, KeyUsage.NONREPUDIATION, KeyUsage.KEY_ENCIPHERMENT)
.addBasicConstraintsExt(false, false, -1)
.addExtension(CertificateBuilder.createIPSubjectAltNameExt(true, "127.0.0.1"))
.build(trustedCert, caKeys.getPrivate(), "MD5WithRSA");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MD5 algorithm is not allowed in TLSv1.3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address this in JDK-8353738

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thanks!

Copy link
Member

@artur-oracle artur-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bridgekeeper
Copy link

bridgekeeper bot commented May 16, 2025

@mpdonova This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

Comment on lines +108 to +109
public static CertificateBuilder newCertificateBuilder(String subjectName,
PublicKey publicKey, PublicKey caKey, KeyUsage... keyUsages)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is to make this a regular constructor and have an additional method that sets the certificate lifetime as a Duration parameter, ex:

new CertificateBuilder(subject, pubKey, caPubKey, keyUsage).withValidity(Duration.ofHours(1));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way it currently is follows the builder pattern better. All of the 'set' methods return this which means if I change this method to a constructor, I'd have to duplicate the "set" code from all those methods.

I like the idea of using Duration but the API for it doesn't really lend itself to this use case. When the certificate is finally created, we write Date objects to the DerOutputStream so I'd have to choose a start time and then calculate the end time based on the duration. It's not hard, but it doesn't seem worth it when the common use case of this class is to generate short-lived certificates for a test. The default one-hour validity will cover the vast majority of tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way it currently is follows the builder pattern better. All of the 'set' methods return this which means if I change this method to a constructor, I'd have to duplicate the "set" code from all those methods.

I'm not sure what you mean - can you give an example? I don't see any advantages of using a static method here, but I agree it works either way.

I like the idea of using Duration but the API for it doesn't really lend itself to this use case. When the certificate is finally created, we write Date objects to the DerOutputStream so I'd have to choose a start time and then calculate the end time based on the duration. It's not hard, but it doesn't seem worth it when the common use case of this class is to generate short-lived certificates for a test. The default one-hour validity will cover the vast majority of tests.

I view this test utility class as being flexible enough for different cases. This is a useful method in that the other parameters are common fields that all certs usually have but it also means I can't use this method to to create a certificate with a longer, or shorter validity period. Alternatively, how about adding another method that hard-codes it as one hour:

CertificateBuilder oneHourValidity()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a constructor, the code would look like this

public CertificateBuilder(String subjectName,
                              PublicKey publicKey, PublicKey caKey, KeyUsage... keyUsages)
            throws CertificateException, IOException {
        factory = CertificateFactory.getInstance("X.509");
        SecureRandom random = new SecureRandom();

        boolean [] keyUsage = new boolean[KeyUsage.values().length];
        for (KeyUsage ku : keyUsages) {
            keyUsage[ku.ordinal()] = true;
        }

        this.subjectName = new X500Principal(subjectName);
        this.publicKey = Objects.requireNonNull(publicKey, "Caught null public key");
        notBefore = (Date)Date.from(Instant.now().minus(1, ChronoUnit.HOURS));
        notAfter = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));
        serialNumber = BigInteger.valueOf(random.nextLong(1000000)+1);
        byte[] keyIdBytes = new KeyIdentifier(publicKey).getIdentifier();
        Extension e = new SubjectKeyIdentifierExtension(keyIdBytes);
        extensions.put(e.getId(), e);

        KeyIdentifier kid = new KeyIdentifier(caKey);
        e = new AuthorityKeyIdentifierExtension(kid, null, null);
        extensions.put(e.getId(), e);

        if (keyUsages.length != 0) {
            e = new KeyUsageExtension(keyUsage);
            extensions.put(e.getId(), e);
        }
    }

it also means I can't use this method to to create a certificate with a longer, or shorter validity period

There are methods to set the not-before and not-after fields. Any field set in this static method can be changed:

newCertificateBuilder(...).notAfter(Date.from(Instant.now().plus(10, TimeUnit.YEARS)));

I don't like using Date here and would be happy to overload it to take Instant as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a constructor, the code would look like this

Ah, I see. Well technically you could call the set methods from the ctor but you would get this-escape compiler warnings, which you probably want to avoid.

it also means I can't use this method to to create a certificate with a longer, or shorter validity period

There are methods to set the not-before and not-after fields. Any field set in this static method can be changed:

newCertificateBuilder(...).notAfter(Date.from(Instant.now().plus(10, TimeUnit.YEARS)));

I don't like using Date here and would be happy to overload it to take Instant as well.

Yes, but I don't think the static method which is generically named newCertificateBuilder should be hard-coding a one hour validity period, that detail should be either in a different method (my preference) or this method should be named more clearly like oneHourCertificateBuilder. I realize this is just a test method, but this is a nicely designed API that has been very useful so I would prefer if we keep the flexibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the default one-hour validity from the builder method and created a setOneHourValidity() method.

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 22, 2025

@mpdonova This pull request has been inactive for more than 8 weeks and will now be automatically closed. If you would like to continue working on this pull request in the future, feel free to reopen it! This can be done using the /open pull request command.

@bridgekeeper bridgekeeper bot closed this Jul 22, 2025
@mpdonova
Copy link
Contributor Author

/open

@openjdk openjdk bot reopened this Jul 22, 2025
@openjdk
Copy link

openjdk bot commented Jul 22, 2025

@mpdonova This pull request is now open

@bridgekeeper
Copy link

bridgekeeper bot commented Aug 26, 2025

@mpdonova This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@openjdk
Copy link

openjdk bot commented Aug 29, 2025

@mpdonova this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout certbuilder
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push

@openjdk openjdk bot added the merge-conflict Pull request has merge conflict with target branch label Aug 29, 2025
@mpdonova mpdonova changed the title 8325766: Review seclibs tests for cert expiry 8325766: Extend CertificateBuilder to create trust and end entity certificates programmatically Aug 29, 2025
@openjdk openjdk bot removed the merge-conflict Pull request has merge conflict with target branch label Aug 29, 2025
@openjdk openjdk bot added the ready Pull request is ready to be integrated label Aug 29, 2025
@mpdonova
Copy link
Contributor Author

mpdonova commented Sep 2, 2025

/integrate

@openjdk
Copy link

openjdk bot commented Sep 2, 2025

Going to push as commit 3184714.
Since your change was applied there have been 51 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Sep 2, 2025
@openjdk openjdk bot closed this Sep 2, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Sep 2, 2025
@openjdk
Copy link

openjdk bot commented Sep 2, 2025

@mpdonova Pushed as commit 3184714.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

5 participants