lightningj-org/lightningj

No subject alternative DNS name matching lnd found

Closed this issue ยท 8 comments

Hi, thanks again for this awesome library. I got everything working locally on one machine (lnd, btcd, my app wrapping lightningj). However, as I'm trying to productionize it and move everything to Docker containers (different IP addresses for each service), I'm seeing this error when I call any gRPC function through lightningj. I believe it has something to do with lnd's generated certs but I've been trying to debug it for a week without any success. Do you have any tips on where to look?

Thanks!

#error {
 :cause "No subject alternative DNS name matching lnd found."
 :via
 [{:type org.lightningj.lnd.wrapper.CommunicationException
   :message "UNAVAILABLE"
   :at [org.lightningj.lnd.wrapper.StatusExceptionWrapper wrap "StatusExceptionWrapper.java" 72]}
  {:type io.grpc.StatusRuntimeException
   :message "UNAVAILABLE"
   :at [io.grpc.stub.ClientCalls toStatusRuntimeException "ClientCalls.java" 210]}
  {:type javax.net.ssl.SSLHandshakeException
   :message "General OpenSslEngine problem"
   :at [io.netty.handler.ssl.ReferenceCountedOpenSslContext$AbstractCertificateVerifier verify "ReferenceCountedOpenSslContext.java" 648]}
  {:type java.security.cert.CertificateException
   :message "No subject alternative DNS name matching lnd found."
   :at [sun.security.util.HostnameChecker matchDNS "HostnameChecker.java" 214]}]
 :trace
 [[sun.security.util.HostnameChecker matchDNS "HostnameChecker.java" 214]
  [sun.security.util.HostnameChecker match "HostnameChecker.java" 96]
  [sun.security.ssl.X509TrustManagerImpl checkIdentity "X509TrustManagerImpl.java" 455]
  [sun.security.ssl.X509TrustManagerImpl checkIdentity "X509TrustManagerImpl.java" 436]
  [sun.security.ssl.X509TrustManagerImpl checkTrusted "X509TrustManagerImpl.java" 252]
  [sun.security.ssl.X509TrustManagerImpl checkServerTrusted "X509TrustManagerImpl.java" 136]
  [io.netty.handler.ssl.ReferenceCountedOpenSslClientContext$ExtendedTrustManagerVerifyCallback verify "ReferenceCountedOpenSslClientContext.java" 221]
  [io.netty.handler.ssl.ReferenceCountedOpenSslContext$AbstractCertificateVerifier verify "ReferenceCountedOpenSslContext.java" 644]
  [io.netty.internal.tcnative.SSL readFromSSL "SSL.java" -2]
  [io.netty.handler.ssl.ReferenceCountedOpenSslEngine readPlaintextData "ReferenceCountedOpenSslEngine.java" 482]
  [io.netty.handler.ssl.ReferenceCountedOpenSslEngine unwrap "ReferenceCountedOpenSslEngine.java" 1020]
  [io.netty.handler.ssl.ReferenceCountedOpenSslEngine unwrap "ReferenceCountedOpenSslEngine.java" 1127]
  [io.netty.handler.ssl.ReferenceCountedOpenSslEngine unwrap "ReferenceCountedOpenSslEngine.java" 1170]
  [io.netty.handler.ssl.SslHandler$SslEngineType$1 unwrap "SslHandler.java" 215]
  [io.netty.handler.ssl.SslHandler unwrap "SslHandler.java" 1215]
  [io.netty.handler.ssl.SslHandler decodeJdkCompatible "SslHandler.java" 1127]
  [io.netty.handler.ssl.SslHandler decode "SslHandler.java" 1162]
  [io.netty.handler.codec.ByteToMessageDecoder decodeRemovalReentryProtection "ByteToMessageDecoder.java" 489]
  [io.netty.handler.codec.ByteToMessageDecoder callDecode "ByteToMessageDecoder.java" 428]
  [io.netty.handler.codec.ByteToMessageDecoder channelRead "ByteToMessageDecoder.java" 265]
  [io.netty.channel.AbstractChannelHandlerContext invokeChannelRead "AbstractChannelHandlerContext.java" 362]
  [io.netty.channel.AbstractChannelHandlerContext invokeChannelRead "AbstractChannelHandlerContext.java" 348]
  [io.netty.channel.AbstractChannelHandlerContext fireChannelRead "AbstractChannelHandlerContext.java" 340]
  [io.netty.channel.DefaultChannelPipeline$HeadContext channelRead "DefaultChannelPipeline.java" 1359]
  [io.netty.channel.AbstractChannelHandlerContext invokeChannelRead "AbstractChannelHandlerContext.java" 362]
  [io.netty.channel.AbstractChannelHandlerContext invokeChannelRead "AbstractChannelHandlerContext.java" 348]
  [io.netty.channel.DefaultChannelPipeline fireChannelRead "DefaultChannelPipeline.java" 935]
  [io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe read "AbstractNioByteChannel.java" 134]
  [io.netty.channel.nio.NioEventLoop processSelectedKey "NioEventLoop.java" 645]
  [io.netty.channel.nio.NioEventLoop processSelectedKeysOptimized "NioEventLoop.java" 580]
  [io.netty.channel.nio.NioEventLoop processSelectedKeys "NioEventLoop.java" 497]
  [io.netty.channel.nio.NioEventLoop run "NioEventLoop.java" 459]
  [io.netty.util.concurrent.SingleThreadEventExecutor$5 run "SingleThreadEventExecutor.java" 858]
  [io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator run "DefaultThreadFactory.java" 138]
  [java.lang.Thread run "Thread.java" 748]]}

Hi
I hope I can help you out. My best guess is that in your certificate you have to have a CN field in the certificate subject matching a DNSName subject alternative name in the same certificate.

Could you send be the content of the certificate by using the command
openssl x509 -in -text

I will try to see how LND is generating its certificate during weekend to see if there is a problem in the autogeneration of the certificate.

As a workaround it's possible to register your own domain name validator in Java using
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);

Not the code above is not safe but might solve it temporarily and you can continue develop until we have found a proper solutions. If setting DefaultHostnameVerifier doesn't work it should be possible to write your own TrustManager and create your own SSLContext.

Also what is the connection string you are using to connect. I think the hostname or the IP have to included as a subject altname name in the TLS certificate (either DNSName or IPAddress).

If the hostname in the certificate is 'lnd' maybe test to add an entry in your host file to point 'lnd' to the correct IP address and use 'lnd' as hostname in your connection.

Thanks so much for the help.

Here's the content of the (dev) certificate:

root@5x7b514342ag:~/.lnd# openssl x509 -in  tls.cert 
-----BEGIN CERTIFICATE-----
MIIB7DCCAZKgAwIBAgIRAMWsOH1gRuyCQDxKCUk4GY0wCgYIKoZIzj0EAwIwODEf
MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMMmE4YTgw
MWQ1ODE5MB4XDTE4MDUzMDIxMDIzMloXDTE5MDcyNTIxMDIzMlowODEfMB0GA1UE
ChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMMmE4YTgwMWQ1ODE5
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaT5DN2QjzzDBC8i9sVoPFB7AgnfN
cewGIyo6DrK2U/J/E6FH/7KHplbAODIc7qr/DG5GQiY1jrxxDiwQIQge/KN9MHsw
DgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wWAYDVR0RBFEwT4IMMmE4
YTgwMWQ1ODE5gglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBAoA
BReHBAr/ADGHBAr/ADqHBKwSAAuHBAoABSMwCgYIKoZIzj0EAwIDSAAwRQIgeNM3
ob4pMk6jQhb10b9EB4i/g7xEO1CC8ALOwvnTP6UCIQCV5DBOZiQAXcA9cvSpQ7z+
XUrahFfd7DArlKTMJJ0g3g==
-----END CERTIFICATE-----

Here's my connection code, the hostname is lnd. I didn't change any settings in lnd, but you're right, maybe I'd need to configure the hostname.

(new SynchronousLndAPI "lnd"
                    10009
                    (File. (str secrets-path "/tls.cert"))
                    (File. (str secrets-path "/admin.macaroon")))

I did try registering a permissive domain validator, but got the same "subject alternative DNS name" error. It could be that my implementation is incorrect. Here's my code for that-- I can try to make a Java version too.

(defn disable-ssl-verification! []
  (try (let [sc (SSLContext/getInstance "SSL")]
         (.init sc nil (into-array [(reify X509TrustManager
                                      (getAcceptedIssuers [this] nil)
                                      (checkClientTrusted [this certs authType])
                                      (checkServerTrusted [this certs authType]))])
                (java.security.SecureRandom.))
         (HttpsURLConnection/setDefaultSSLSocketFactory (.getSocketFactory sc))
         (HttpsURLConnection/setDefaultHostnameVerifier (reify HostnameVerifier
                                                          (verify [this hostname session] true))))
       (catch Exception e
         (.printStackTrace e))))

(defn set-socket-factory! []
  (let [cert-manager (make-array X509TrustManager 1)
        sc           (SSLContext/getInstance "SSL")]
    (aset cert-manager 0
          (proxy [X509TrustManager][]
            (getAcceptedIssuers [])
            (checkClientTrusted [_ _])
            (checkServerTrusted [_ _])))
    (.init sc nil cert-manager (java.security.SecureRandom.))
    (HttpsURLConnection/setDefaultSSLSocketFactory (.getSocketFactory sc))))

(disable-ssl-verification!)
(set-socket-factory!)

I think I see the problem, in your certificate you have:
Subject:
O=lnd autogenerated cert, CN=2a8a801d5819
Subject Altname:
DNS:2a8a801d5819, DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, IP Address:10.0.5.23, IP Address:10.255.0.49, IP Address:10.255.0.58, IP Address:172.18.0.11, IP Address:10.0.5.35

There is no entry for 'lnd' can you try create a hostfile entry for 2a8a801d5819 and try to connect to host: 2a8a801d5819?

Alternatively regenerate the certificate to have CN=lnd and a DNS:lnd as subject altname.

Tip: You can see the contents in your certificate with:

openssl x509 -in tls.crt -text

Wow that's great! Adding the hostfile entry fixed the DNS error. Would the proper fix be for lnd to generate a cert with CN=lnd and DNS:lnd like you said? I'm looking into how to do that.

Yes that would be the best fix. My best guess is that the CN and DNS field is taken from the hostname of the node. If it is docker I'm uncertain where this is set but in linux it is usually /etc/hostname.

I asked around on the LND slack and turns out there's a flag to lnd to set the domain, --tlsextradomain=<domain>. I've verified that it works in this case. Btw here's the code around cert generation.

Thanks so much for your help! LN is in its infancy and once it catches on there will be a lot of people who find lightningj useful- it's nice to not have to mess with gRPC details.