pkl icon indicating copy to clipboard operation
pkl copied to clipboard

`project package` throws `InvalidAlgorithmParameterException` when adding `Package.apiTests`

Open StefMa opened this issue 11 months ago • 3 comments

Given this source code: https://github.com/StefMa/pkl-gha/tree/9d5b220b0a2dcfb971a56b875124a89f6acff611

If you checkout the code and run pkl test tests/*.pkl it runs the tests and they pass. If you package the code, it works as well ./pkl project package. As soon as you uncomment line 9 in PklProject (enabling apiTests) and run the package command, the tests pass but afterwards it throws an exception:

InvalidAlgorithmParameterException
module com.github.GitHubAction.tests (file:///~pkl/pkl-github/tests/GitHubAction.pkl, line 1)
  basic ✅
An unexpected error has occurred. Would you mind filing a bug report?

org.pkl.core.packages.PackageLoadError: Exception when making request `GET https://github.com/stefma/[email protected]`:
Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at org.pkl.core.packages.PackageResolvers$AbstractPackageResolver.openExternalUri(PackageResolvers.java:211)
        at org.pkl.core.packages.PackageResolvers$AbstractPackageResolver.getDependencyMetadataAndComputeChecksum(PackageResolvers.java:103)
        at org.pkl.core.project.ProjectPackager.checkAlreadyPublishedPackage(ProjectPackager.java:175)
        at org.pkl.core.project.ProjectPackager.doPackage(ProjectPackager.java:162)
        at org.pkl.core.project.ProjectPackager.createPackages(ProjectPackager.java:122)
        at org.pkl.cli.CliProjectPackager.doRun(CliProjectPackager.kt:88)
        at org.pkl.commons.cli.CliCommand.run(CliCommand.kt:45)
        at org.pkl.cli.commands.ProjectCommand$Companion$PackageCommand.run(ProjectCommand.kt:145)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:198)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:211)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:211)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:18)
        at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:400)
        at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:397)
        at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:415)
        at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:440)
        at org.pkl.cli.Main$main$1.invoke(Main.kt:52)
        at org.pkl.cli.Main$main$1.invoke(Main.kt:40)
        at org.pkl.commons.cli.CliMainKt.cliMain(CliMain.kt:31)
        at org.pkl.cli.Main.main(Main.kt:40)
Caused by: javax.net.ssl.SSLException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at [email protected]/sun.security.ssl.Alert.createSSLException(Alert.java:133)
        at [email protected]/sun.security.ssl.TransportContext.fatal(TransportContext.java:378)
        at [email protected]/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
        at [email protected]/sun.security.ssl.TransportContext.fatal(TransportContext.java:316)
        at [email protected]/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1708)
        at [email protected]/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:470)
        at [email protected]/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:426)
        at [email protected]/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:589)
        at [email protected]/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:187)
        at [email protected]/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1665)
        at [email protected]/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
        at [email protected]/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529)
        at [email protected]/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:308)
        at org.pkl.core.packages.PackageResolvers$AbstractPackageResolver.openExternalUri(PackageResolvers.java:206)
        ... 19 more
Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at [email protected]/sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:101)
        at [email protected]/sun.security.validator.Validator.getInstance(Validator.java:181)
        at [email protected]/sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:309)
        at [email protected]/sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:183)
        at [email protected]/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:198)
        at [email protected]/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
        at [email protected]/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1341)
        at [email protected]/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1232)
        at [email protected]/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1175)
        at [email protected]/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
        at [email protected]/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:480)
        at [email protected]/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:458)
        at [email protected]/sun.security.ssl.TransportContext.dispatch(TransportContext.java:201)
        at [email protected]/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
        at [email protected]/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)
        at [email protected]/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1421)
        at [email protected]/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
        ... 27 more
Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at [email protected]/java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
        at [email protected]/java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
        at [email protected]/java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
        at [email protected]/sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:98)
        ... 43 more

I have the feeling the baseUri is not correct. But actually I also don't know what should be a current basiUri. I also tried package://pkg.pkl-lang.org/github.com/stefma/pkl-gha/\(name) (which sounds to me more correct) or even set a "valid" version (0.0.1). Still no success, same error but with a different URL:

org.pkl.core.packages.PackageLoadError: Exception when making request `GET https://pkg.pkl-lang.org/github.com/stefma/pkl-gha/[email protected]`:

All of this sounds strange to me for two reasons:

  1. Calling this, will successfully download my package (not even sure if this is intended), so why it is an exception?
  2. Why is there called a URL (with the new version) in this process anyways? In fact, when I do package I do a new release, obvious that this is not yet available.

StefMa avatar Mar 08 '24 12:03 StefMa

The package command will check if there is already an existing package that exists, but with a different checksum. If it exists, it means that you need to bump your package's version.

You can disable this behavior using the flag --skip-publish-check.

The error that you're seeing suggests that you've set up Pkl with custom CA certificates, but there are problems with your custom certificates.

Do you have a ~/.pkl/cacerts directory on your machine? If so, you will want to ensure that it has all of the root certs that you intend to trust.

bioball avatar Mar 08 '24 18:03 bioball

Could you check if Pkl built from the main branch gives a better error message that doesn't include "Would you mind filing a bug report?"? I'm asking because my HttpClient PR was recently merged.

translatenix avatar Mar 08 '24 19:03 translatenix

@bioball

The package command will check if there is already an existing package that exists, but with a different checksum. If it exists, it means that you need to bump your package's version.

But, why does this only happen when I add the apiTests config? 🤔 Shouldn't it do it all the time?

The error that you're seeing suggests that you've set up Pkl with custom CA certificates

Hmm.. How do i set up such a certificate? If I did so, I never did this on purpose actually.

Do you have a ~/.pkl/cacerts directory on your machine?

Nope, its not there 🙃

Screenshot 2024-03-09 at 9 22 43 PM

@translatenix

Could you check if Pkl built from the main branch

I checked out https://github.com/apple/pkl/tree/8dc258ef7d795809079c0346995ca052fd6aeaed (This includes your changes). Then I build the cli with ./gradlew :pkl-cli:build and run VERSION=0.0.1 /path/to/checkout/pkl-repo/pkl-cli/build/executable/jpkl project package. I still get an error, also still including the An unexpected error has occurred. Would you mind filing a bug report? message 😬

org.pkl.core.packages.PackageLoadError:
module com.github.GitHubAction.tests (file:///~pkl/pkl-github/tests/GitHubAction.pkl, line 1)
  basic ✅
An unexpected error has occurred. Would you mind filing a bug report?

org.pkl.core.packages.PackageLoadError: Received unexpected status code `301` when making request `GET https://pkg.pkl-lang.org/github.com/stefma/pkl-gha/[email protected]`.
        at org.pkl.core.packages.PackageResolvers$AbstractPackageResolver.openExternalUri(PackageResolvers.java:208)
        at org.pkl.core.packages.PackageResolvers$AbstractPackageResolver.getDependencyMetadataAndComputeChecksum(PackageResolvers.java:101)
        at org.pkl.core.project.ProjectPackager.checkAlreadyPublishedPackage(ProjectPackager.java:177)
        at org.pkl.core.project.ProjectPackager.doPackage(ProjectPackager.java:164)
        at org.pkl.core.project.ProjectPackager.createPackages(ProjectPackager.java:124)
        at org.pkl.cli.CliProjectPackager.doRun(CliProjectPackager.kt:89)
        at org.pkl.commons.cli.CliCommand.run(CliCommand.kt:39)
        at org.pkl.cli.commands.ProjectCommand$Companion$PackageCommand.run(ProjectCommand.kt:145)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:198)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:211)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:211)
        at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:18)
        at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:400)
        at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:397)
        at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:415)
        at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:440)
        at org.pkl.cli.Main$main$1.invoke(Main.kt:52)
        at org.pkl.cli.Main$main$1.invoke(Main.kt:40)
        at org.pkl.commons.cli.CliMainKt.cliMain(CliMain.kt:31)
        at org.pkl.cli.Main.main(Main.kt:40)

StefMa avatar Mar 09 '24 20:03 StefMa

I did a closer look into this and found the following:

In case there is an apiTest property in the PklProject, the ProjectCommand creates the CliProjectPackager. The CliProjectPackager gets the baseOptions (this is a instance of CliBaseOptions) as arguments. The baseOptions function will call getEffectiveCaCertificates() to populate the certificates. In my case, without a custom certificate, the "default" one from the resources directory.

The CliProjectPackager will, in case the package.apiTests property is available, create a new instance of the CliTestRunner. This Runner gets a copy of the CliBaseOptions. The options, put to the CliTestRunner, are just a copy of the same options as the CliProjectPackager have. Thus, it includes the same instance of the caCertificates property

Since the base class of both (the CliProjectPackager and the CliTestRunner) is the CliCommand, which has a init function that calls CertificateUtils.setupAllX509CertificatesGlobally followed by CertificateUtils.toInputStream and CertificateUtils.generateCertificates, it will crash at this point because the exact same InputStream is used but this InputStream is already closed.

This is my visual output (println debugging 😁 ):

I call CertificateUtils.setupAllX509CertificatesGlobally from CliProjectPackager
toInputStream! sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@3d299e3
Generating certificates.. Already closed?: 218384
I call CertificateUtils.setupAllX509CertificatesGlobally from CliTestRunner
// Note the same instance! 👇 
toInputStream! sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@3d299e3 
// At this point, the stream is already closed 👇 
Generating certificates.. Already closed?: 0

This is the diff on top of https://github.com/apple/pkl/tree/11f07d1ce8fe3af1e07b3472781f62805b8c419c:

diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectPackager.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectPackager.kt
index da000ad..d84e09f 100644
--- a/pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectPackager.kt
+++ b/pkl-cli/src/main/kotlin/org/pkl/cli/CliProjectPackager.kt
@@ -41,6 +41,8 @@ class CliProjectPackager(
     val normalizeApiTests = apiTests.map { project.projectDir.resolve(it).toUri() }
     val testRunner =
       CliTestRunner(
+        // This might be a fix?
+        // cliOptions.copy(sourceModules = normalizeApiTests, projectDir = project.projectDir, caCertificates = emptyList()),
         cliOptions.copy(sourceModules = normalizeApiTests, projectDir = project.projectDir),
         testOptions = testOptions,
         consoleWriter = consoleWriter,
diff --git a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt
index 948c445..c26c6d8 100644
--- a/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt
+++ b/pkl-commons-cli/src/main/kotlin/org/pkl/commons/cli/CliCommand.kt
@@ -32,6 +32,7 @@ import org.pkl.core.util.IoUtils
 abstract class CliCommand(protected val cliOptions: CliBaseOptions) {
   init {
     if (cliOptions.caCertificates.isNotEmpty()) {
+      println("I call CertificateUtils.setupAllX509CertificatesGlobally from ${this::class.simpleName}")
       CertificateUtils.setupAllX509CertificatesGlobally(cliOptions.caCertificates)
     }
   }
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/CertificateUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/CertificateUtils.java
index 4d7f750..eabf83a 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/CertificateUtils.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/CertificateUtils.java
@@ -51,11 +51,14 @@ public class CertificateUtils {
   }
 
   private static InputStream toInputStream(Object cert) throws IOException {
+    System.out.println("toInputStream! " + cert);
     if (cert instanceof Path) {
       var pathCert = (Path) cert;
       return Files.newInputStream(pathCert);
     }
     if (cert instanceof InputStream) {
+      // "Here is the problem".
+      // We're returning the same input stream, but it is already closed.
       return (InputStream) cert;
     }
     throw new IllegalArgumentException(
@@ -65,8 +68,9 @@ public class CertificateUtils {
   }
 
   private static Collection<X509Certificate> generateCertificates(InputStream inputStream)
-      throws CertificateException {
+      throws CertificateException, IOException {
     //noinspection unchecked
+    System.out.println("Generating certificates.. Already closed?: " + inputStream.available());
     return (Collection<X509Certificate>)
         CertificateFactory.getInstance("X.509").generateCertificates(inputStream);
   }

I don't know where to fix this. Possible options would be:

  • Ignore the certificate for the TestRunner (using emptyList())?
  • Creating a new InputStream?
  • Do not close the InputStream?
  • Something else?! 🤷

Maybe this helps a bit 🙃

StefMa avatar Mar 14 '24 08:03 StefMa

FWIW, CertificateUtils is gone in HEAD.

translatenix avatar Mar 14 '24 09:03 translatenix

Didn't I said I tested your changes? 😖 I just tested it again with HEAD as well as the mentioned commit. Seems to work now 🎉

Time for another release 🙃

StefMa avatar Mar 14 '24 11:03 StefMa