/*
* dnssecjava - a DNSSEC validating stub resolver for Java
* Copyright (c) 2013-2015 Ingo Bauersachs
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jitsi.dnssec.validator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.powermock.api.mockito.PowerMockito.doAnswer;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.whenNew;
import java.io.IOException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.util.Properties;
import org.jitsi.dnssec.AlwaysOffline;
import org.jitsi.dnssec.PrepareMocks;
import org.jitsi.dnssec.TestBase;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Message;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
@RunWith(PowerMockRunner.class)
@PrepareForTest({DNSKEYRecord.class, NSEC3ValUtils.class})
public class TestNsec3ValUtils extends TestBase {
@Test(expected = IllegalArgumentException.class)
public void testTooLargeIterationCountMustThrow() {
Properties config = new Properties();
config.put("org.jitsi.dnssec.nsec3.iterations.512", Integer.MAX_VALUE);
NSEC3ValUtils val = new NSEC3ValUtils();
val.init(config);
}
@Test
public void testInvalidIterationCountMarksInsecure() throws IOException {
Properties config = new Properties();
config.put("org.jitsi.dnssec.nsec3.iterations.1024", 0);
config.put("org.jitsi.dnssec.nsec3.iterations.2048", 0);
config.put("org.jitsi.dnssec.nsec3.iterations.4096", 0);
resolver.init(config);
Message response = resolver.send(createMessage("www.wc.nsec3.ingotronic.ch./A"));
assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NOERROR, response.getRcode());
assertEquals("failed.nsec3_ignored", getReason(response));
}
@Test
public void testNsec3WithoutClosestEncloser() throws IOException {
Message m = resolver.send(createMessage("gibtsnicht.gibtsnicht.nsec3.ingotronic.ch./A"));
Message message = messageFromString(m.toString().replaceAll("((UDUMPS9J6F8348HFHH2FAED6I9DDE0U6)|(NTV3QJT4VQDVBPB6BNOVM40NMKJ3H29P))\\.nsec3.*", ""));
add("gibtsnicht.gibtsnicht.nsec3.ingotronic.ch./A", message);
Message response = resolver.send(createMessage("gibtsnicht.gibtsnicht.nsec3.ingotronic.ch./A"));
assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.SERVFAIL, response.getRcode());
assertEquals("failed.nxdomain.nsec3_bogus", getReason(response));
}
@Test
public void testNsec3NodataChangedToNxdomainIsBogus() throws IOException {
Message m = resolver.send(createMessage("a.b.nsec3.ingotronic.ch./MX"));
Message message = messageFromString(m.toString().replaceAll("status: NOERROR", "status: NXDOMAIN"));
add("a.b.nsec3.ingotronic.ch./A", message);
Message response = resolver.send(createMessage("a.b.nsec3.ingotronic.ch./A"));
assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.SERVFAIL, response.getRcode());
assertEquals("failed.nxdomain.nsec3_bogus", getReason(response));
}
@Test
public void testNsec3ClosestEncloserIsDelegation() throws IOException {
// hash(n=9.nsec3.ingotronic.ch.,it=10,s=1234)=6jl2t4i2bb7eilloi8mdhbf3uqjgvu4s
Message cem = resolver.send(createMessage("9.nsec3.ingotronic.ch./A"));
Record delegationNsec = null;
Record delegationNsecSig = null;
for (RRset set : cem.getSectionRRsets(Section.AUTHORITY)) {
// hash(n=sub.nsec3.ingotronic.ch.,it=10,s=1234)=5RFQOLI81S6LKQTUG5HLI19UVJNKUL3H
if (set.getName().toString().startsWith("5RFQOLI81S6LKQTUG5HLI19UVJNKUL3H")) {
delegationNsec = set.first();
delegationNsecSig = (Record)set.sigs().next();
break;
}
}
Message m = resolver.send(createMessage("a.sub.nsec3.ingotronic.ch./A"));
String temp = m.toString().replaceAll("^sub\\.nsec3.*", "");
// hash(n=sub.nsec3.ingotronic.ch.,it=11,s=4321)=8N8QLBCUIH7R2BG7DMCJ5AEE63K4KVUA
temp = temp.replaceAll("8N8QLBCUIH7R2BG7DMCJ5AEE63K4KVUA.*", "");
Message message = messageFromString(temp);
message.addRecord(delegationNsec, Section.AUTHORITY);
message.addRecord(delegationNsecSig, Section.AUTHORITY);
add("a.sub.nsec3.ingotronic.ch./A", message);
Message response = resolver.send(createMessage("a.sub.nsec3.ingotronic.ch./A"));
assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.SERVFAIL, response.getRcode());
assertEquals("failed.nxdomain.nsec3_bogus", getReason(response));
}
@Test
@AlwaysOffline
public void testNsec3ClosestEncloserIsInsecureDelegation() throws IOException {
Message response = resolver.send(createMessage("a.unsigned.nsec3.ingotronic.ch./A"));
assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NXDOMAIN, response.getRcode());
assertEquals("failed.nxdomain.nsec3_insecure", getReason(response));
}
@Test
@PrepareMocks("prepareTestPublicKeyLoadingException")
public void testPublicKeyLoadingException() throws IOException {
Message response = resolver.send(createMessage("www.wc.nsec3.ingotronic.ch./A"));
assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NOERROR, response.getRcode());
assertEquals("failed.nsec3_ignored", getReason(response));
}
private int invocationCount = 0;
public void prepareTestPublicKeyLoadingException() throws Exception {
whenNew(DNSKEYRecord.class).withNoArguments().thenAnswer(new Answer<DNSKEYRecord>() {
@Override
public DNSKEYRecord answer(InvocationOnMock invocationOnMock) throws Throwable {
DNSKEYRecord dr = spy(Whitebox.invokeConstructor(DNSKEYRecord.class));
doAnswer(new Answer<PublicKey>() {
@Override
public PublicKey answer(InvocationOnMock invocation) throws Throwable {
DNSKEYRecord dr = (DNSKEYRecord)invocation.getMock();
invocationCount++;
if (dr.getName().equals(Name.fromConstantString("nsec3.ingotronic.ch.")) && invocationCount == 11) {
throw Whitebox.invokeConstructor(DNSSEC.DNSSECException.class, "mock-test");
}
return (PublicKey)invocation.callRealMethod();
}
}).when(dr).getPublicKey();
return dr;
}
});
}
@Test
public void testNsecEcdsa256() throws IOException {
Provider[] providers = Security.getProviders("KeyFactory.EC");
Assume.assumeTrue(providers != null && providers.length > 0);
Message response = resolver.send(createMessage("www.wc.nsec3-ecdsa256.ingotronic.ch./A"));
assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NOERROR, response.getRcode());
}
@Test
public void testNsecEcdsa384() throws IOException {
Provider[] providers = Security.getProviders("KeyFactory.EC");
Assume.assumeTrue(providers != null && providers.length > 0);
Message response = resolver.send(createMessage("www.wc.nsec3-ecdsa384.ingotronic.ch./A"));
assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NOERROR, response.getRcode());
}
}