/*
 * 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());
    }
}
