Graal: Calling https URL throws Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

Created on 4 Mar 2019  路  7Comments  路  Source: oracle/graal

My native image makes a HTTPS call to a remote resource and throws a Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

The code is pretty straightforward:

@Controller("/")
public class ExampleController {

    private static final Log LOG = LogFactory.getLog(ExampleController.class);
    private HttpClient httpClient;

    public ExampleController() {
        this.httpClient = httpClientFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
    }

    @Get("/http")
    public String httpClient() throws IOException {

        String url = "https://www.google.com/search?q=httpClient";
        HttpGet request = new HttpGet(url);

        // add request header
        HttpResponse response = httpClient.execute(request);

        System.out.println("Response Code : "
                + response.getStatusLine().getStatusCode());
        return "ok";
    }
}

I've followed the documentation at https://github.com/oracle/graal/blob/master/substratevm/URL-PROTOCOLS.md and https://github.com/oracle/graal/blob/master/substratevm/JCA-SECURITY-SERVICES.md to enable security services, adding --enable-https

The only thing I haven't done is adding libsunec.so. I've looked for it in various JDK installations (1.0.0-rc-12-grl, 8.0.202-zulu, and Oracle's jdk1.8.0_201.jdk), however none of the contained the .so file.

Is that the reason why I'm getting that error? If so, where can I get the needed .so file? An updated of the related doc would come in handy in case it is not shipped by default with any jdk.

native-image

Most helpful comment

The cacerts file is also a run time dependency so you need to package it for run time: RUN zip -j function.zip bootstrap libsunec.so cacerts server. Setting
javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword
in ExampleController() doesn't work, I think it is too late, before the classes that need it are already initialized. You need to set them in bootstrap via ./server -Djavax.net.ssl.trustStore=cacerts -Djavax.net.ssl.trustStorePassword=changeit.

With these changes and using the libsunec.so from graalvm-ce-1.0.0-rc13/jre/lib/amd64/libsunec.so the image builds and runs succesfully and http://127.0.0.1:3000/http returns ok.

All 7 comments

@codependent does this fix work for you https://github.com/oracle/graal/issues/768#issuecomment-451776587? Otherwise, can you point to the repo where I can replicate this?

By the way libsunec.so is in GraalVM under graalvm-ce-1.0.0-rc12/jre/lib/amd64/libsunec.so. Various JDK distributions might have it in different locations. However, I don't think missing it is related to that issue.

Regarding libsunec.so, I am using the mac graal distribuition which doesn't include it. I downloaded the linux version and copied it to my project (https://github.com/codependent/graal-app).

I didn't set the java.library.path as the file is copied to /home/application which is the workdir of the image (Dockerfile -> WORKDIR /home/application). However if I start the container manually it still shows:

WARNING: The sunec native library, required by the SunEC provider, could not be loaded. This library is usually shipped as part of the JDK and can be found under /jre/lib//libsunec.so. It is loaded at run time via System.loadLibrary("sunec"), the first time services from SunEC are accessed. To use this provider's services the java.library.path system property needs to be set accordingly to point to a location that contains libsunec.so. Note that if java.library.path is not set it defaults to the current working directory.

On another hand, I followed the comment linked above and set the trustore location (/home/application/cacerts) and password pointing at a copy of the cacerts included in the graal distribution. Previously I put it in the root of the project so that the Dockerfile COPY statement put it into the container at /home/application/cacerts.

The trustore is setup in the ExampleController constructor:

@Controller("/")
public class ExampleController {

    private static final Log LOG = LogFactory.getLog(ExampleController.class);
    private ObjectMapper mapper = new ObjectMapper()
            .disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
            .disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS)
            .enable(JsonParser.Feature.ALLOW_COMMENTS)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    private static final HttpClientFactory<ConnectionManagerAwareHttpClient> httpClientFactory = new
            ApacheHttpClientFactory();

    private HttpClient httpClient;

    public ExampleController() {
        this.httpClient = httpClientFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
        LOG.info("init");
        LOG.info("Registering truststore in ExampleController <init>");
        System.setProperty("javax.net.ssl.trustStore","/home/application/cacerts");
        System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
        final ConnectionManagerFactory<HttpClientConnectionManager> cmFactory = new ApacheConnectionManagerFactory();
        final HttpClientConnectionManager cm = cmFactory.create(HttpClientSettings.adapt(new ClientConfiguration()));
        ClientConnectionManagerFactory.wrap(cm);
    }

    ...

    @Get("/http")
    public String httpClient() throws IOException {
        String url = "https://www.google.com/search?q=httpClient";
        HttpGet request = new HttpGet(url);
        HttpResponse response = httpClient.execute(request);
        LOG.info("Response Code : {}" + response.getStatusLine().getStatusCode());
        return "ok";
    }

However I'm still getting the exception:

Caused by: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
        at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200)
        at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120)
        at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104)
        at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:89)
        ... 65 more

To reproduce:

  1. export AWS_DEFAULT_REGION=eu-central-1
  2. docker build . -t graal-app
  3. ./sam-local.sh
  4. Access http://localhost:3000/http

libsunec.so is a run time dependency, so you need to pack it in the function.zip: RUN zip -j function.zip bootstrap libsunec.so server.

I cannot replicate the InvalidAlgorithmParameterException. Here is what I get:

$ ./sam-local.sh
2019-03-06 16:50:12 Mounting MyServiceFunction at http://127.0.0.1:3000/{proxy+} [GET, DELETE, PUT, POST, HEAD, OPTIONS, PATCH]
2019-03-06 16:50:12 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-03-06 16:50:12  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2019-03-06 16:50:21 Invoking not.used.in.provided.runtime (provided)
2019-03-06 16:50:22 Decompressing /home/cdrtz/projects/github/gh-1034/graal-app/build/function.zip

Fetching lambci/lambda:provided Docker container image......
2019-03-06 16:50:24 Mounting /tmp/tmpH_JzU2 as /var/task:ro inside runtime container
00:50:25.187 [main] INFO  i.m.f.a.p.AbstractLambdaContainerHandler - Starting Lambda Container Handler
00:50:25.187 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [function]
START RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Version: $LATEST
Mar 07, 2019 12:50:25 AM graal.app.ExampleController <init>
INFO: init
Mar 07, 2019 12:50:25 AM graal.app.ExampleController <init>
INFO: Registering truststore in ExampleController <init>
2019-03-06 16:50:40 Function 'MyServiceFunction' timed out after 15 seconds
2019-03-06 16:50:40 Function returned an invalid response (must include one of: body, headers or statusCode in the response object). Response received: 
2019-03-06 16:50:40 127.0.0.1 - - [06/Mar/2019 16:50:40] "GET /http HTTP/1.1" 502 -
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 51306)
Traceback (most recent call last):
  File "/usr/lib/python2.7/SocketServer.py", line 596, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 331, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python2.7/SocketServer.py", line 654, in __init__
    self.finish()
  File "/usr/lib/python2.7/SocketServer.py", line 713, in finish
    self.wfile.close()
  File "/usr/lib/python2.7/socket.py", line 283, in close
    self.flush()
  File "/usr/lib/python2.7/socket.py", line 307, in flush
    self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------

It looks like I needed to specify --docker-network host to the sam command.

The cacerts file is also a run time dependency so you need to package it for run time: RUN zip -j function.zip bootstrap libsunec.so cacerts server. Setting
javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword
in ExampleController() doesn't work, I think it is too late, before the classes that need it are already initialized. You need to set them in bootstrap via ./server -Djavax.net.ssl.trustStore=cacerts -Djavax.net.ssl.trustStorePassword=changeit.

With these changes and using the libsunec.so from graalvm-ce-1.0.0-rc13/jre/lib/amd64/libsunec.so the image builds and runs succesfully and http://127.0.0.1:3000/http returns ok.

Was this page helpful?
0 / 5 - 0 ratings