Smack 4.3.0 DNSSEC is not working at root-zone DS record validation

With reference to to the site: https://www.cloudflare.com/dns/dnssec/how-dnssec-works/

The test is based on aTalk, ported to use smack v4.3.0 and mindDNS v0.3.2. The DNSSEC validation is carried out using the babai.ru, and the same observation is reconfirmed on another domain in .ru zone.

Following describes the actual validation process tracing, until the problem is encountered:

1. smack and miniDns perform the RRSIG signatures validation using ZSK and KSK for domain (babai.ru) in  zone (.ru) without any problem.
2. The problem occur when DnssClient is about to perform DS record validation with the root servers while generating the query. At which point, the variables and contents are as follow:
a. rrsig.signerName = "."
b. rrsig.typeCovered="DS"
c. q="ru.	IN	DS"
I am assuming that rrsig.singerName "'." is referring to the root server.

3a. While executing DnssecClient.verifySignedRecords(q, rrsig, records);
3b. it performs DnssecClient.queryDnssec(rrsig.signerName, TYPE.DNSKEY);

4a. The exception occurs when trying to perform rawAce = MiniDnsIdna.toASCII(name); where name = "."
4b. at idnaTransformator.toASCII(string);  i.e. string="."

5. The error message "Invalid input to ASCII: ." was thrown by DefaultIdnaTransformator class.
Look like it fails to handle dot "." transformation.

I did not goes into full understanding of the miniDNS source, I apologize if my comments above do deviate from the actual signature verification flow.

Trying to be absolute sure, I did a recompilation of miniDns-core library with some source changes. However it seems AS or aTalk gradle setting configuration are causing problem. The generation of apk although without problem, it appear to have the multidex packed incorrectly with classes not related to smack or mindDns when running on android; aTalk runs but the user login process cannot be performed - still try to figure out why.

Note: On smack 4.2.4 with miniDNS 0.2.4, it throws a different error message i.e. “Validation of 2 DNSKEY records failed: Signature is invalid”. Look similar unable to validate the DNSKEY with the root server?

I am not able to reproduce this. Using MiniDNS’s REPL on the 0.3 branch:

$ ./repl
dr.resolveDnssecReliable("babai.ru", classOf[A])

works.

Please always show the stacktrace of the involved exceptions.

Are you sure that you don’t mix MiniDNS components with different versions?

Here is the stack trace for the problem:

08-21 05:30:24.472 5834-6720/org.atalk.android E/aTalk: [20937] util.account.LoginManager.run().318 Failed to register protocol provider. 
    java.lang.IllegalArgumentException: Invalid input to toASCII: .
        at java.net.IDN.toASCII(IDN.java:112)
        at java.net.IDN.toASCII(IDN.java:134)
        at org.minidns.idna.DefaultIdnaTransformator.toASCII(DefaultIdnaTransformator.java:19)
        at org.minidns.idna.MiniDnsIdna.toASCII(MiniDnsIdna.java:18)
        at org.minidns.dnsname.DnsName.<init>(DnsName.java:126)
        at org.minidns.dnsname.DnsName.from(DnsName.java:331)
        at org.minidns.dnsname.DnsName.from(DnsName.java:327)
        at org.minidns.dnsmessage.Question.<init>(Question.java:100)
        at org.minidns.dnssec.DnssecClient.queryDnssec(DnssecClient.java:92)
        at org.minidns.dnssec.DnssecClient.verifySignedRecords(DnssecClient.java:362)
        at org.minidns.dnssec.DnssecClient.verifySignatures(DnssecClient.java:301)
        at org.minidns.dnssec.DnssecClient.verifyAnswer(DnssecClient.java:162)
        at org.minidns.dnssec.DnssecClient.verify(DnssecClient.java:152)
        at org.minidns.dnssec.DnssecClient.performVerification(DnssecClient.java:112)
        at org.minidns.dnssec.DnssecClient.queryDnssec(DnssecClient.java:94)
        at org.minidns.dnssec.DnssecClient.verifySecureEntryPoint(DnssecClient.java:408)
        at org.minidns.dnssec.DnssecClient.verifyAnswer(DnssecClient.java:178)
        at org.minidns.dnssec.DnssecClient.verify(DnssecClient.java:152)
        at org.minidns.dnssec.DnssecClient.performVerification(DnssecClient.java:112)
        at org.minidns.dnssec.DnssecClient.queryDnssec(DnssecClient.java:94)
        at org.minidns.dnssec.DnssecClient.verifySignedRecords(DnssecClient.java:362)
        at org.minidns.dnssec.DnssecClient.verifySignatures(DnssecClient.java:301)
        at org.minidns.dnssec.DnssecClient.verifyAnswer(DnssecClient.java:162)
        at org.minidns.dnssec.DnssecClient.verify(DnssecClient.java:152)
        at org.minidns.dnssec.DnssecClient.performVerification(DnssecClient.java:112)
        at org.minidns.dnssec.DnssecClient.queryDnssec(DnssecClient.java:94)
        at org.minidns.dnssec.DnssecClient.verifySecureEntryPoint(DnssecClient.java:408)
        at org.minidns.dnssec.DnssecClient.verifyAnswer(DnssecClient.java:178)
        at org.minidns.dnssec.DnssecClient.verify(DnssecClient.java:152)
        at org.minidns.dnssec.DnssecClient.performVerification(DnssecClient.java:112)
        at org.minidns.dnssec.DnssecClient.queryDnssec(DnssecClient.java:94)
        at org.minidns.dnssec.DnssecClient.verifySignedRecords(DnssecClient.java:362)
        at org.minidns.dnssec.DnssecClient.verifySignatures(DnssecClient.java:301)
        at org.minidns.dnssec.DnssecClient.verifyAnswer(DnssecClient.java:162)
        at org.minidns.dnssec.DnssecClient.verify(DnssecClient.java:152)
        at org.minidns.dnssec.DnssecClient.performVerification(DnssecClient.java:112)
        at org.minidns.dnssec.DnssecClient.queryDnssec(DnssecClient.java:100)
        at org.minidns.hla.DnssecResolverApi.resolve(DnssecResolverApi.java:65)
        at org.minidns.hla.ResolverApi.resolve(ResolverApi.java:109)
        at org.minidns.hla.ResolverApi.resolveSrv(ResolverApi.java:184)
        at org.jivesoftware.smack.util.dns.minidns.MiniDnsResolver.lookupSRVRecords0(MiniDnsResolver.java:74)
        at org.jivesoftware.smack.util.dns.DNSResolver.lookupSRVRecords(DNSResolver.java:53)
        at org.jivesoftware.smack.util.DNSUtil.resolveDomain(DNSUtil.java:155)
        at org.jivesoftware.smack.util.DNSUtil.resolveXMPPServiceDomain(DNSUtil.java:115)
        at org.jivesoftware.smack.AbstractXMPPConnection.populateHostAddresses(AbstractXMPPConnection.java:650)
        at org.jivesoftware.smack.tcp.XMPPTCPConnection.connectUsingConfiguration(XMPPTCPConnection.java:559)
        at org.jivesoftware.smack.tcp.XMPPTCPConnection.connectInternal(XMPPTCPConnection.java:895)
        at org.jivesoftware.smack.AbstractXMPPConnection.connect(AbstractXMPPConnection.java:409)
        at net.java.sip.communicator.impl.protocol.jabber.ProtocolProviderServiceJabberImpl.connectAndLogin(ProtocolProviderServiceJabberImpl.java:972)
        at net.java.sip.communicator.impl.protocol.jabber.ProtocolProviderServiceJabberImpl.initializeConnectAndLogin(ProtocolProviderServiceJabberImpl.java:712)
    	at net.java.sip.communicator.impl.protocol.jabber.ProtocolProviderServiceJabberImpl.

I just realized that you are on Android. This could indeed be a behavioral difference between the Java SE runtime and Android’s IDN.toASCII().

If you have the time and motorization, try what toASCII() returns or throws if the argument is "." (a string only consisting of a single dot) and "" ( the empty string) on Android and on Java SE. If the former throws but the later works on Android, then you could fix this by installing a wrapping IdnaTransformator which special cases "." to "".

By inserting break point, I am able to verify that toASCII(“”) does not throw exception.

then you could fix this by installing a wrapping IdnaTransformator which special cases “.” to “”.

Need some help on how to do this?

Previously I was trying to replace the auto-dependent minidns-core library included by Gradle with my modified mididns-core library; Android Studio apk built and installation are without any problem; aTalk also execute without any exceptions. However some other non-mididns related classes/features seem to be missing, and aTalk cannot get all the registered accounts and login. Very strange behavior.
I have done the similar library replacement for smack-core in earlier aTalk releases, and atalk run without any problem.

Create a new class implementing IdnaTransformator which patches DefaultIdnaTransformator like this:

--- a/minidns-core/src/main/java/org/minidns/idna/DefaultIdnaTransformator.java
+++ b/minidns-core/src/main/java/org/minidns/idna/DefaultIdnaTransformator.java
@@ -12,10 +12,16 @@ package org.minidns.idna;
 
 import java.net.IDN;
 
+import org.minidns.dnsname.DnsName;
+
 public class DefaultIdnaTransformator implements IdnaTransformator {
 
     @Override
     public String toASCII(String input) {
+        if (DnsName.ROOT.ace.equals(input)) {
+            return DnsName.ROOT.ace;
+        }
+
         return IDN.toASCII(input);

then set your patched transformator using MiniDnsIdna.setActiveTransformator().

I am looking at the Android source code but the AOSP HEAD does not match your stacktrace. On which Android version do you see this particular stacktrace?

The stack trace is tied to Android API-26 i.e. ./android-sdk/sources/android-26/java/net/IDA.java

I have implemented per your recommendation in aTalk source package directory i.e.
org.minidns.idna

Surprisingly AS links in this new class into apk, replacing the one in the minidns-core library without me doing anything extra. Previously AS usually will complain duplicated classes and linker will failed.

I have tested, the new changes work for both DNSSEC and DNSSEC & DANE signature verification.
Thanks for your help.

The IllegalArgumentException thrown by toASCII() should also wrap a StringPrepParseException. Would be great if you could show us this exception and their stacktrace too.

Related AOSP bug (please star it): https://issuetracker.google.com/issues/113070416
Related MiniDNS issue for a workaround: https://github.com/MiniDNS/minidns/issues/89

As the call to setActiveTransformator() is not static, I try to use the following statement to setActiveTransformator() on start up of aTalk.

(new MiniDnsIdna()).setActiveTransformator(new AndroidIdnaTransformator());

However it is not working, and the current DefaultIdnaTransformator is still active.
Do I have to set this for each login account during login process, and how do I ensure it does take effect?

==== update =====
Look like the auto-replacement of the class by Gradle is only working while in debug. For office release build AS transformClassesWithMultidexlistForDebug refuses to accept class duplication.

Odd, even though the static declration is missing (will be fixed soon), this should work.

I am not sure why you need to use any kind of build-system based replacement feature for this and which class you would need to duplicate.

Odd, even though the static declration is missing (will be fixed soon), this should work.
Just realize that it actually throw the following exception as the method check for
(idnaTransformator != null) which is always true

    public void setActiveTransformator(IdnaTransformator idnaTransformator) {
        if (idnaTransformator != null) {
            throw new IllegalArgumentException();
        }
        MiniDnsIdna.idnaTransformator = idnaTransformator;
    }
08-25 09:35:32.876 27855-27890/org.atalk.android E/aTalk: [702] org.atalk.impl.osgi.framework.BundleImpl.start().296 Error starting bundle: net.java.sip.communicator.impl.dns.DnsUtilActivator@7d7690e
    java.lang.IllegalArgumentException
        at org.minidns.idna.MiniDnsIdna.setActiveTransformator(MiniDnsIdna.java:27)
        at net.java.sip.communicator.impl.dns.DnsUtilActivator.start(DnsUtilActivator.java:62)
        at org.atalk.impl.osgi.framework.BundleImpl.start(BundleImpl.java:293)
        at org.atalk.impl.osgi.framework.launch.FrameworkImpl.startLevelChanged(FrameworkImpl.java:343)
        at org.atalk.impl.osgi.framework.startlevel.FrameworkStartLevelImpl$Command.run(FrameworkStartLevelImpl.java:127)
        at org.atalk.impl.osgi.framework.AsyncExecutor.runInThread(AsyncExecutor.java:119)
        at org.atalk.impl.osgi.framework.AsyncExecutor.access$000(AsyncExecutor.java:25)
        at org.atalk.impl.osgi.framework.AsyncExecutor$1.run(AsyncExecutor.java:228)
08-25 09:35:32.878 27855-27890/org.atalk.android E/aTalk: [702] org.atalk.impl.osgi.framework.launch.FrameworkImpl.startLevelChanged().347 Error changing start level
    org.osgi.framework.BundleException: BundleActivator.start
        at org.atalk.impl.osgi.framework.BundleImpl.start(BundleImpl.java:310)
        at org.atalk.impl.osgi.framework.launch.FrameworkImpl.startLevelChanged(FrameworkImpl.java:343)
        at org.atalk.impl.osgi.framework.startlevel.FrameworkStartLevelImpl$Command.run(FrameworkStartLevelImpl.java:127)
        at org.atalk.impl.osgi.framework.AsyncExecutor.runInThread(AsyncExecutor.java:119)
        at org.atalk.impl.osgi.framework.AsyncExecutor.access$000(AsyncExecutor.java:25)
        at org.atalk.impl.osgi.framework.AsyncExecutor$1.run(AsyncExecutor.java:228)
     Caused by: java.lang.IllegalArgumentException
        at org.minidns.idna.MiniDnsIdna.setActiveTransformator(MiniDnsIdna.java:27)
        at net.java.sip.communicator.impl.dns.DnsUtilActivator.start(DnsUtilActivator.java:62)
        at org.atalk.impl.osgi.framework.BundleImpl.start(BundleImpl.java:293)
        at org.atalk.impl.osgi.framework.launch.FrameworkImpl.startLevelChanged(FrameworkImpl.java:343) 
        at org.atalk.impl.osgi.framework.startlevel.FrameworkStartLevelImpl$Command.run(FrameworkStartLevelImpl.java:127) 
        at org.atalk.impl.osgi.framework.AsyncExecutor.runInThread(AsyncExecutor.java:119) 
        at org.atalk.impl.osgi.framework.AsyncExecutor.access$000(AsyncExecutor.java:25) 
        at org.atalk.impl.osgi.framework.AsyncExecutor$1.run(AsyncExecutor.java:228)

I am not sure why you need to use any kind of build-system based replacement feature for this and which class you would need to duplicate.

Actually I have gone away from this approach, as I am no comfortable with AS/Gradle whether this works all the time. I decided to move to use setActiveTransformator() but face problem as reported.

Previously I was trying to replace the auto-dependent minidns-core library included by Gradle with my modified mididns-core library; Android Studio apk built and installation are without any problem; aTalk also execute without any exceptions. However some other non-mididns related classes/features seem to be missing, and aTalk cannot get all the registered accounts and login. Very strange behavior.

I have traced the problem and again it is due to AS/Gradle, one the called method get incorrectly linked to an unrelated class/method. A very similar problem that I once faced.

However when I change the version of the minidns-core to 0.3.3-SNAPSHOTS a minute ago, then the built apk works. Very strange.

With the newly built apk, when the test the DNSSES and DANE, I see the following warning message in the log i.e. Does this mean DANE verification failed?
However when using online tool, the verification is OK

DANE TLSA Server Checker.pdf (119.7 KB)

08-25 17:17:50.424 14288-15813/org.atalk.android W/aTalk: [1278] org.minidns.dane.DaneVerifier.checkCertificateMatches() TLSA certificate usage byte 3 is not supported while verifying babai.ru

Right, will be fixed in a future MiniDNS version. You could still set the IdnaTransformator using reflection, bypassing the faulty condition.

No, it is just a warning the the ‘usage’ byte value is not know to MiniDNS. Would be pretty bad design if MiniDNS would inform you just by logging a warning that the verification failed, wouldn’t it?

But I’m not sure why you see it for ‘3’. That value is known to all released MiniDNS versions.

Right, will be fixed in a future MiniDNS version. You could still set the IdnaTransformator using reflection, bypassing the faulty condition.

I have replaced the dependent minidns-core 0.3.2 from maven with my local compiled minidns-core-0.3.3-SNAPSHOTS. AS seems happy once I up version the library.

But I’m not sure why you see it for ‘3’. That value is known to all released MiniDNS versions.

The problem is due to the following statement in DaneVerifier where certVsage == null. However the TLSA record contains the following values:

3 1 1 5079a8929a1782bc2626ec8718628152ca4c15bc4e49b94aca377be148b5e108

When I trace into TLSA.class, followings are my observations (Something seems NOK ):

  1. In public static TLSA parse(DataInputStream dis, int length) throws IOException:
    1a. this.certificateAssociation contains various byte value of 32 bytes in length
    1b. local certificateAssociation cotains 32 bytes of all ‘0’
    1c. TLSA(certUsage, selector, matchingType, certificateAssociation) call pass certificateAssociation in 1b

  2. In TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte certificateAssociation):
    2a. Upon enter into this method, exception is being throw i.e. “Method threw ‘java.lang.NullPointerException’ exception. Cannot evaluate org.minidns.record.TLSA.toString()”
    2b. Passed parameters contain the following values:
    byte certUsageByte = 3
    byte selectorByte = 1
    byte matchingTypeByte = 1
    byte certificateAssociation = null => reason for NPE in 2a?
    2b. All static arrays are empty i.e. CERT_USAGE_LUT, SELECTOR_LUT and MATCHING_TYPE_LUT
    2c. Hence this.certUsage, this.selector and this.matchingType variables are all assigned with null values.
    2c. Hence the reason for the warning above.

======== DaneVerifier =========
    private static boolean checkCertificateMatches(X509Certificate cert, TLSA tlsa, String hostName) throws CertificateException {
        if (tlsa.certUsage == null) {
            LOGGER.warning("TLSA certificate usage byte " + tlsa.certUsageByte + " is not supported while verifying " + hostName);
            return false;
        }

======== TLSA class  =========
    public static TLSA parse(DataInputStream dis, int length) throws IOException {
        byte certUsage = dis.readByte();
        byte selector = dis.readByte();
        byte matchingType = dis.readByte();
        byte[] certificateAssociation = new byte[length - 3];
        if (dis.read(certificateAssociation) != certificateAssociation.length) throw new IOException();
        return new TLSA(certUsage, selector, matchingType, certificateAssociation);
    }

    TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte[] certificateAssociation) {
        this.certUsageByte = certUsageByte;
        this.certUsage = CERT_USAGE_LUT.get(certUsageByte);

        this.selectorByte = selectorByte;
        this.selector = SELECTOR_LUT.get(selectorByte);

        this.matchingTypeByte = matchingTypeByte;
        this.matchingType = MATCHING_TYPE_LUT.get(matchingTypeByte);

        this.certificateAssociation = certificateAssociation;
    }

Right, but the question is how certUsage could be null if certUsageByte is 3. Which should be a value found in the corresponding LUT.

Sorry I made a mistake, certificateAssociation contains the actual byte values read in in dis.read(certificateAssociation);

byte certificateAssociation = null => reason for NPE in 2a?

During debug break, if I place the cursor over the ‘this’ in the CertUsage constructor, the debugger shows the same exception message i.e. “Method threw ‘java.lang.NullPointerException’ exception. Cannot evaluate org.minidns.record.TLSA.toString()”.

Apparently ‘this’ is being referred to ‘TLSA.this’. Very strange. If I compiled the library and change to CertUsage.this, the exception is still get throw on entry in the method TLSA(), but cursor on CertUsage.this, does not show the exception message anymore. May be it is AS debugger problem.

       private CertUsage(byte byteValue) {
            this.byteValue = byteValue;
            CERT_USAGE_LUT.put(byteValue, this);
        }

I have also trying to understand how each array e.g. CERT_USAGE_LUT is being initialized before use.
My understanding is that enum is a compilation option and get initialized during compilation phase.
But I am not sure will compiler also run your private constructor (as above) so as to populate the map.

Whereas
private static final Map<Byte, CertUsage> CERT_USAGE_LUT = new HashMap<>();

is only get executed during run time. If the CERT_USAGE_LUT get filled during compilation, then this statement will re-init itself to an empty map.

Not sure if my observation is correct.

Attached is the minidns-core.patch that I applied for the local library generation that fixes all the problems including the TLSA class file. However the following exception is still happened. I think the reason is onEntry to the method, all the variables used in toString() is still null - uninitialize. During trace debug, the exception message is cleared and replaced with the tlsa record string, after executed the method last statement i.e. this.certificateAssociation = certificateAssociation;

In TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte certificateAssociation):
2a. Upon enter into this method, exception is being throw i.e. “Method threw ‘java.lang.NullPointerException’ exception. Cannot evaluate org.minidns.record.TLSA.toString()”

minidns-core.patch (4.8 KB)

This latest patch also fixes the

In TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte certificateAssociation):
2a. Upon enter into this method, exception is being throw i.e. “Method threw ‘java.lang.NullPointerException’ exception. Cannot evaluate org.minidns.record.TLSA.toString()”

minidns-core.patch (7.6 KB)

Could you please, please, always include the stacktrace when talking about exceptions?

No, this is not how Java works The static blocks in a class get execute when the class is loaded.

Could you please, please, always include the stacktrace when talking about exceptions?

Sorry, there is no exception trace to capture In this particular case when it occurs, and it just continue the execution without aborting the app. During tracing I set a break point right at the method entry first statement i.e.

TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte[] certificateAssociation)()

The exception message just appears next to the ‘this’ in the Debug window. It seems that android/java performs a toString() on entry to TLSA. Since certificateAssociation is null at this point, hence the exception message. My patch check for (certificateAssociation != null) before append the parameter. This fixed and the exception message does not appear any more.

No the is not how Java works. The static blocks in a class get execute when the class is loaded.

Attached is the AS Debug window info captured on break point; it shows the exception on toString() and all LUTs maps are empty.

I also placed a breakpoints within the CertUsage etc private construction, they never get executed during run time. However I tend to agreed with you that the static method should executed during run time. I am not sure if my observations are actually due to AS built environment or Android OS JIT etc.