Skip to content
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

Allow root certificates to be configured at run time of native image #2831

Closed
wants to merge 1 commit into from
Closed

Conversation

jaikiran
Copy link
Contributor

@jaikiran jaikiran commented Sep 6, 2020

The commit in this PR introduces the "option#2" support that has been noted and requested in #1999.
More specifically, this commit now allows users to (optionally) specify a path to root certificates using the (standard) javax.net.ssl.trustStore system property at run time, while launching the native application.

Details about this change:

  • This commit continues to embed the build time root certificates into the native image. This is intentional so as to not force users to always specify the root certificate location at run time.
  • The commit introduces logic that's run at image run time which checks for a non-empty value for javax.net.ssl.trustStore system property. If it finds one, then instead of using the embedded certs, it goes ahead and uses the (existing) JRE infrastructure to parse and use the root certificates pointed to by value of that system property.
  • Before this commit, there is a piece of code which initializes the sun.security.util.UntrustedCertificates at build time. That class internally loads a lib/security/blacklisted.certs file, which is relative to the Java installation used at build time of the image. This file contains the blacklisted certificates and is used to validate the "trusted" certificate. The commit in this PR (intentionally) continues to allow this logic to stay and thus continues to use the build time blacklist file for decision making at run time. IMO, this should be fine because unlike new certificates getting added to a trust store, a blacklisted certificate rarely (is it ever?) is considered to be good again. So carrying over this information to the run time, IMO, is a good thing. However, there isn't a way to add new certificates to this blacklist at runtime and I don't think that's something that should be in the scope of this specific change.
  • There's this (existing) code in this TrustStoreManagerFeature which I don't fully understand:
RuntimeClassInitialization.initializeAtBuildTime(org.jcp.xml.dsig.internal.dom.XMLDSigRI.class);

I have given a short glance at that org.jcp.xml.dsig.internal.dom.XMLDSigRI to see if the change in this commit impacts in any way, that specific class and whether anything should be done to handle it. With my limited knowledge of that class, I haven't been able to decide if anything is needed for that one. So please do let me know if something needs to be done there.

  • Some javadoc and comment changes have been done to explain this change

Finally, I've done some manual testing with this change. I used the following Java class to build a native image from:

import java.net.URL;
import java.io.InputStream;
import java.net.*;
import java.io.*;
import java.lang.reflect.*;

public class HelloWorld {
	public static void main(String[] args) throws Exception { 
		final URL targetURL = new URL("https://google.com/");
        HttpURLConnection responseConnection = (HttpURLConnection) targetURL.openConnection();
        responseConnection.setRequestMethod("GET");
        try (final InputStream is = responseConnection.getInputStream()) {
        	is.read();
        }
        System.out.println("Successfully communicated with " + targetURL);
	} 
}

Pretty trivial code which connects to an endpoint using HTTPS. With these change, I built the native image:

native-image -H:EnableURLProtocols=https,http -cp .  HelloWorld

and then run the following tests:

(for each of these tests, I included -Djavax.net.debug=trustmanager,certpath so that necessary log messages are printed to verify the trust management)

  1. Without passing any -Djavax.net.ssl.trustStore, this should internally use the embedded certs and the program should successfully complete:
./helloworld  -Djavax.net.debug=trustmanager,certpath 
Successfully communicated with https://google.com/

(absence of logs from the "trustmanager" layer and a successfull completion is an indication that embedded root certs were used for trust management)

  1. Passing an empty value to -Djavax.net.ssl.trustStore, this should be same as not passing that system property and should use the embedded certs:
./helloworld  -Djavax.net.debug=trustmanager,certpath -Djavax.net.ssl.trustStore=
Successfully communicated with https://google.com/
  1. Passing a proper valid truststore to the -Djavax.net.ssl.trustStore, this should use the root certs pointed to by the path that's passed to this property:
./helloworld  -Djavax.net.debug=trustmanager,certpath -Djavax.net.ssl.trustStore=/home/me/openjdk/jdk-11.0.8+10/Contents/Home/lib/security/cacerts
javax.net.ssl|DEBUG|01|main|2020-09-06 17:14:40.414 IST|TrustStoreManager.java:112|trustStore is: /home/me/openjdk/jdk-11.0.8+10/Contents/Home/lib/security/cacerts
trustStore type is: pkcs12
trustStore provider is: 
the last modified time is: Wed Jul 15 14:28:35 IST 2020
javax.net.ssl|DEBUG|01|main|2020-09-06 17:14:40.414 IST|TrustStoreManager.java:311|Reload the trust store
javax.net.ssl|DEBUG|01|main|2020-09-06 17:14:40.416 IST|TrustStoreManager.java:318|Reload trust certs
javax.net.ssl|DEBUG|01|main|2020-09-06 17:14:40.416 IST|TrustStoreManager.java:323|Reloaded 91 trust certs
Successfully communicated with https://google.com/
  1. Passing an invalid path (file exists but content isn't certs) to -Djavax.net.ssl.trustStore, this should fail the program during SSL communication:
./helloworld  -Djavax.net.debug=trustmanager,certpath -Djavax.net.ssl.trustStore=/home/me/somerandomfile.txt 
javax.net.ssl|DEBUG|01|main|2020-09-06 17:16:42.794 IST|TrustStoreManager.java:112|trustStore is: /home/me/somerandomfile.txt
trustStore type is: pkcs12
trustStore provider is: 
the last modified time is: Wed Jul 15 14:28:35 IST 2020
javax.net.ssl|DEBUG|01|main|2020-09-06 17:16:42.794 IST|TrustStoreManager.java:311|Reload the trust store
javax.net.ssl|DEBUG|01|main|2020-09-06 17:16:42.797 IST|TrustManagerFactoryImpl.java:70|SunX509: skip default keystore (
"throwable" : {
  java.io.IOException: toDerInputStream rejects tag type 47
  	at sun.security.util.DerValue.toDerInputStream(DerValue.java:873)
  	at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1994)
  	at sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:222)
  	at java.security.KeyStore.load(KeyStore.java:1479)
  	at sun.security.ssl.TrustStoreManager$TrustAnchorManager.loadKeyStore(TrustStoreManager.java:365)
  	at sun.security.ssl.TrustStoreManager$TrustAnchorManager.getTrustedCerts(TrustStoreManager.java:313)
  	at sun.security.ssl.TrustStoreManager.getTrustedCerts(TrustStoreManager.java:125)
  	at sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:49)
  	at javax.net.ssl.TrustManagerFactory.init(TrustManagerFactory.java:278)
  	at sun.security.ssl.SSLContextImpl$DefaultManagersHolder.getTrustManagers(SSLContextImpl.java:1053)
  	at sun.security.ssl.SSLContextImpl$DefaultManagersHolder.<clinit>(SSLContextImpl.java:1023)
  	at com.oracle.svm.core.classinitialization.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:351)
  	at com.oracle.svm.core.classinitialization.ClassInitializationInfo.initialize(ClassInitializationInfo.java:271)
  	at sun.security.ssl.SSLContextImpl$DefaultSSLContext.<init>(SSLContextImpl.java:1198)
  	at java.lang.reflect.Constructor.newInstance(Constructor.java:490)
  	at java.security.Provider.newInstanceUtil(Provider.java:154)
  	at java.security.Provider$Service.newInstance(Provider.java:1818)
  	at sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
  	at sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
  	at javax.net.ssl.SSLContext.getInstance(SSLContext.java:168)
  	at javax.net.ssl.SSLContext.getDefault(SSLContext.java:99)
  	at javax.net.ssl.SSLSocketFactory.getDefault(SSLSocketFactory.java:123)
  	at javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory(HttpsURLConnection.java:335)
  	at javax.net.ssl.HttpsURLConnection.<init>(HttpsURLConnection.java:292)
  	at sun.net.www.protocol.https.HttpsURLConnectionImpl.<init>(HttpsURLConnectionImpl.java:100)
  	at sun.net.www.protocol.https.Handler.openConnection(Handler.java:62)
  	at sun.net.www.protocol.https.Handler.openConnection(Handler.java:57)
  	at java.net.URL.openConnection(URL.java:1074)
  	at HelloWorld.main(HelloWorld.java:10)}

)
Exception in thread "main" java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: sun.security.ssl.SSLContextImpl$DefaultSSLContext)
	at javax.net.ssl.DefaultSSLSocketFactory.throwException(SSLSocketFactory.java:263)
	at javax.net.ssl.DefaultSSLSocketFactory.createSocket(SSLSocketFactory.java:270)
	at sun.net.www.protocol.https.HttpsClient.createSocket(HttpsClient.java:413)
	at sun.net.NetworkClient.doConnect(NetworkClient.java:162)
	at sun.net.www.http.HttpClient.openServer(HttpClient.java:474)
	at sun.net.www.http.HttpClient.openServer(HttpClient.java:569)
	at sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:265)
	at sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:372)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:191)
	at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1187)
	at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1081)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:177)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1587)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1515)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
	at HelloWorld.main(HelloWorld.java:13)
Caused by: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: sun.security.ssl.SSLContextImpl$DefaultSSLContext)
	at java.security.Provider$Service.newInstance(Provider.java:1825)
	at sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
	at sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
	at javax.net.ssl.SSLContext.getInstance(SSLContext.java:168)
	at javax.net.ssl.SSLContext.getDefault(SSLContext.java:99)
	at javax.net.ssl.SSLSocketFactory.getDefault(SSLSocketFactory.java:123)
	at javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory(HttpsURLConnection.java:335)
	at javax.net.ssl.HttpsURLConnection.<init>(HttpsURLConnection.java:292)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.<init>(HttpsURLConnectionImpl.java:100)
	at sun.net.www.protocol.https.Handler.openConnection(Handler.java:62)
	at sun.net.www.protocol.https.Handler.openConnection(Handler.java:57)
	at java.net.URL.openConnection(URL.java:1074)
	at HelloWorld.main(HelloWorld.java:10)
Caused by: java.security.KeyStoreException: problem accessing trust store
	at sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:73)
	at javax.net.ssl.TrustManagerFactory.init(TrustManagerFactory.java:278)
	at sun.security.ssl.SSLContextImpl$DefaultManagersHolder.getTrustManagers(SSLContextImpl.java:1053)
	at sun.security.ssl.SSLContextImpl$DefaultManagersHolder.<clinit>(SSLContextImpl.java:1023)
	at com.oracle.svm.core.classinitialization.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:351)
	at com.oracle.svm.core.classinitialization.ClassInitializationInfo.initialize(ClassInitializationInfo.java:271)
	at sun.security.ssl.SSLContextImpl$DefaultSSLContext.<init>(SSLContextImpl.java:1198)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:490)
	at java.security.Provider.newInstanceUtil(Provider.java:154)
	at java.security.Provider$Service.newInstance(Provider.java:1818)
	... 12 more
Caused by: java.io.IOException: toDerInputStream rejects tag type 47
	at sun.security.util.DerValue.toDerInputStream(DerValue.java:873)
	at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1994)
	at sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:222)
	at java.security.KeyStore.load(KeyStore.java:1479)
	at sun.security.ssl.TrustStoreManager$TrustAnchorManager.loadKeyStore(TrustStoreManager.java:365)
	at sun.security.ssl.TrustStoreManager$TrustAnchorManager.getTrustedCerts(TrustStoreManager.java:313)
	at sun.security.ssl.TrustStoreManager.getTrustedCerts(TrustStoreManager.java:125)
	at sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:49)
	... 21 more
  1. Passing an invalid path (non-existent file) to -Djavax.net.ssl.trustStore, this should fail the program during SSL communication:
./helloworld  -Djavax.net.debug=trustmanager,certpath -Djavax.net.ssl.trustStore=/home/me/foo 
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.256 IST|TrustStoreManager.java:161|Inaccessible trust store: /home/me/foo
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.257 IST|TrustStoreManager.java:161|Inaccessible trust store: /home/me/foo
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.257 IST|TrustStoreManager.java:112|trustStore is: 
trustStore type is: pkcs12
trustStore provider is: 
the last modified time is: Thu Jan 01 05:30:00 IST 1970
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.257 IST|TrustStoreManager.java:311|Reload the trust store
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.257 IST|TrustStoreManager.java:343|No available key store
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.257 IST|TrustStoreManager.java:318|Reload trust certs
javax.net.ssl|DEBUG|01|main|2020-09-06 17:20:05.257 IST|TrustStoreManager.java:323|Reloaded 0 trust certs
Exception in thread "main" javax.net.ssl.SSLException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
	at sun.security.ssl.Alert.createSSLException(Alert.java:133)
	at sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
	at sun.security.ssl.TransportContext.fatal(TransportContext.java:269)
	at sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
	at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1306)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:401)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:567)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1587)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1515)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
	at HelloWorld.main(HelloWorld.java:13)
Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
	at sun.security.validator.PKIXValidator.<init>(PKIXValidator.java:102)
	at sun.security.validator.Validator.getInstance(Validator.java:181)
	at sun.security.ssl.X509TrustManagerImpl.getValidator(X509TrustManagerImpl.java:300)
	at sun.security.ssl.X509TrustManagerImpl.checkTrustedInit(X509TrustManagerImpl.java:176)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:189)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
	at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:629)
	at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:464)
	at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:360)
	at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
	at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
	at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422)
	at sun.security.ssl.TransportContext.dispatch(TransportContext.java:183)
	at sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
	at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1144)
	at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1055)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:395)
	... 6 more
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:99)
	... 22 more

/cc @christianwimmer

@jaikiran
Copy link
Contributor Author

jaikiran commented Sep 6, 2020

Not sure why the checkstyle job is failing when I've explicitly disabled checkstyle on tha specific block of code:

----------

1. ERROR in /home/travis/build/oracle/graal/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java (at line 158)

	Set<X509Certificate> getTrustedCerts(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor) throws Exception {

	                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The method getTrustedCerts(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor) from the type Target_sun_security_ssl_TrustStoreManager_TrustAnchorManager can be declared as static

----------

2. ERROR in /home/travis/build/oracle/graal/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java (at line 158)

	Set<X509Certificate> getTrustedCerts(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor) throws Exception {

	                                                                                                    ^^^^^^^^^^

The value of the parameter descriptor is not used

----------

3. ERROR in /home/travis/build/oracle/graal/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java (at line 163)

	KeyStore getKeyStore(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor) throws Exception {

	         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The method getKeyStore(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor) from the type Target_sun_security_ssl_TrustStoreManager_TrustAnchorManager can be declared as static

----------

4. ERROR in /home/travis/build/oracle/graal/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java (at line 163)

	KeyStore getKeyStore(Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor descriptor) throws Exception {

	                                                                                    ^^^^^^^^^^

The value of the parameter descriptor is not used

----------

4 problems (4 errors)

Compiling com.oracle.svm.core with ecj-daemon(JDK 1.8) failed

1 build tasks failed

…an alternate location for root certificates at native image run time
@jaikiran
Copy link
Contributor Author

jaikiran commented Sep 6, 2020

Never mind, I think my latest push should solve that job failure.

@jaikiran
Copy link
Contributor Author

jaikiran commented Sep 7, 2020

So the final checkstyle error that's remaining is:

Checkstyle ends with 1 errors.

/home/travis/build/oracle/graal/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java:103: Using 'synchronized' is not allowed.

I would like to understand why synchronized isn't allowed in that part of the code, because I suspect I might be missing something fundamental here.

TrustStoreManagerSupport(Set<X509Certificate> trustedCerts, KeyStore trustedKeyStore) {
this.trustedCerts = trustedCerts;
this.trustedKeyStore = trustedKeyStore;
}

static boolean useEmbeddedCerts() {
if (truststoreSysPropSet != null) {

Choose a reason for hiding this comment

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

I don't think you can do this that easily:

  • The trustStore system property can be changed (set and unset) at any time at run time. You need to always reflect the current correct value.
  • Separating the check "is system property set" from the actual usage of the property in the JDK code has the problem of race conditions: if the system property is changed in between, the wrong value is used.
    So I don't think you can have a proper implementation without deep integration into TrustStoreManager and TrustStoreDescriptor of the JDK.


@Alias
static Target_sun_security_ssl_TrustStoreManager_TrustStoreDescriptor createInstance() {
return null;

Choose a reason for hiding this comment

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

Please always add comments for substituted methods what the original did vs. what the new version is doing. I cannot judge right now if returning null here (and in the methods below) is correct or not.


// we do not need these paths (corresponding to the build time environment) to be carried over
// to the run time
@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) private static String defaultStorePath = null;

Choose a reason for hiding this comment

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

I hope that with the proper integration of the "default store", these fields can actually be marked as @Delete rather than @RecomputeFieldValue. That would assure us that the JDK code for the default store is really properly replaced with the "store from the image heap".

@matthyx
Copy link

matthyx commented Dec 8, 2020

@jaikiran @christianwimmer what's the status of this PR?
If you need help, I can take over and hopefully make it through.

@jaikiran
Copy link
Contributor Author

jaikiran commented Dec 9, 2020

Hello @matthyx, I've been swamped at work for the past several weeks and I am not sure when I can get back to discussing these suggestions from @christianwimmer.

If you need help, I can take over and hopefully make it through.

Please do go ahead. I will be watching this PR and the repo and can help in small parts if/when needed, if time permits.

@christianwimmer
Copy link

@matthyx As I commented already on the PR, I do not think this PR contains a valid implementation. A proper solution is for sure a good bit of work.

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

Successfully merging this pull request may close these issues.

4 participants