Skip to content

Replace native tool deps (keytool/openssl/certbot) with pure Java APIs#403

Open
lagergren wants to merge 2 commits intomasterfrom
lagergren/cert
Open

Replace native tool deps (keytool/openssl/certbot) with pure Java APIs#403
lagergren wants to merge 2 commits intomasterfrom
lagergren/cert

Conversation

@lagergren
Copy link
Copy Markdown
Contributor

Summary

  • Replaced all ProcessBuilder calls to keytool, openssl, and certbot in xRTCertificateManager with pure Java equivalents
  • keytooljava.security.KeyStore API (JDK)
  • openssl (RSA keygen, CSR generation, PEM→PKCS12 conversion) → BouncyCastle + JDK crypto
  • certbot (ACME/Let's Encrypt protocol) → acme4j
  • New versioned dependencies: acme4j 3.5.1, BouncyCastle 1.82 (declared in libs.versions.toml)
  • Extracted KeyStoreOperations utility class with no XVM runtime dependencies
  • Added 13 unit tests covering all keystore operations
  • Fat jar size increased from 5.7 MB → 15 MB (BouncyCastle is ~7.8 MB)

Notes

  • The certbot/certbot-staging provider names are preserved for backward compatibility
  • TODO comments flag exception-swallowing patterns and lack of proper logging framework
  • ACME challenge flow uses the same .challenge webroot directory as before

Test plan

  • All 13 KeyStoreOperationsTest tests pass (self-signed cert, symmetric key, password, password change, key extraction, deletion)
  • Manual test with self-signed certificate provider
  • Manual test with Let's Encrypt staging provider (requires network + domain)

🤖 Generated with Claude Code

lagergren and others added 2 commits March 6, 2026 00:11
…e Java APIs

xRTCertificateManager previously shelled out to keytool, openssl, and certbot
via ProcessBuilder — unversioned system binaries that the build couldn't declare
as dependencies. This replaces all three with pure Java:

- keytool → java.security.KeyStore API
- openssl (RSA keygen, CSR, PEM→PKCS12) → BouncyCastle + JDK crypto
- certbot (ACME/Let's Encrypt) → acme4j

New versioned dependencies in libs.versions.toml: acme4j 3.5.1, BouncyCastle 1.82.

Extracted KeyStoreOperations utility class (no XVM dependencies) with unit tests
covering self-signed cert generation, symmetric key, password storage, keystore
password change, key extraction, and entry deletion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lagergren lagergren marked this pull request as ready for review March 6, 2026 10:17
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I have not tested any of it with the platform; please hold on the merge

Copy link
Copy Markdown
Collaborator

@ggleyzer ggleyzer left a comment

Choose a reason for hiding this comment

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

In addition to two reported issues I still didn't have time to test this in the real world, having to talk to the "real" CA authority. After the changes I requested are submitted I'm going to test it with the platform.

return xException.makeHandle(frame,
"Unsupported certificate provider: " + hProvider.getStringValue());
private void pollAuthUntilValid(Authorization auth, String sDomain) throws Exception {
for (int i = 0; i < MAX_POLL_ATTEMPTS && auth.getStatus() != Status.VALID; i++) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a very questionable implementation. Sleeping for five seconds for something that may come back in a millisecond seems to be wrong. I realize that Process.waitFor() we are using now seems to use the same approach, but (i) they only sleep for 100 millis; (ii) jdk can improve or make it native at any time; (ii) one of the points of this exercise is to improve the current behavior.


if (cert instanceof X509Certificate x509Cert) {
var session = new Session(acmeServerUri(fStaging));
var accountKeyPair = KeyPairUtils.createKeyPair(2048);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems to be wrong; the key pair we pass to the org.shredzone.acme4j.Certificate.revoke() API must be a key pair associated with the certificate being revoked, but a randomly generated one

: frameCaller.assignValue(iReturn,
xArray.makeByteArrayHandle(key.getEncoded(), Mutability.Constant));
} catch (Throwable e) {
// TODO: catching Throwable and discarding the cause is bad practice; the
Copy link
Copy Markdown
Collaborator

@ggleyzer ggleyzer Mar 13, 2026

Choose a reason for hiding this comment

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

To be fair we don't fully discard it; the message we log carries at least some information about it. Clearly we could improve it though

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants