Skip to content
This repository has been archived by the owner on Feb 6, 2022. It is now read-only.

Commit

Permalink
Merge pull request #22 from ibauersachs/eddsa
Browse files Browse the repository at this point in the history
Add support for EdDSA 25519 / EdDSA 448 DNSKEYs (RFC 8080)
  • Loading branch information
ibauersachs committed Feb 18, 2020
2 parents cc5ed75 + ab79fc2 commit ab53e6a
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 70 deletions.
5 changes: 2 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
<url>https://github.com/ibauersachs/dnssecjava</url>

<properties>
<powermock.version>2.0.2</powermock.version>
<powermock.version>2.0.5</powermock.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<checkstyle.config.location>checkstyle.xml</checkstyle.config.location>
<slf4j.version>1.7.30</slf4j.version>
</properties>

<dependencies>
<dependency>
<groupId>dnsjava</groupId>
<artifactId>dnsjava</artifactId>
<version>3.0.0-next.1</version>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/jitsi/dnssec/validator/NSEC3ValUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ private boolean validIterations(SRRset nsec, KeyCache keyCache) {
case Algorithm.ECC_GOST:
keysize = 512;
break;
case Algorithm.ED25519:
keysize = 256;
break;
case Algorithm.ED448:
keysize = 456;
break;
default:
return false;
}
Expand Down
49 changes: 26 additions & 23 deletions src/main/java/org/jitsi/dnssec/validator/ValUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,15 @@ public class ValUtils {
private Properties config = null;
private boolean digestHardenDowngrade = true;
private boolean hasGost;
private boolean hasEd25519;
private boolean hasEd448;

/** Creates a new instance of this class. */
public ValUtils() {
this.verifier = new DnsSecVerifier();
hasGost = Security.getProviders("MessageDigest.GOST3411") != null;
hasEd25519 = Security.getProviders("KeyFactory.Ed25519") != null;
hasEd448 = Security.getProviders("KeyFactory.Ed448") != null;
}

/**
Expand Down Expand Up @@ -134,6 +139,8 @@ public static void setCanonicalNsecOwner(SRRset set, RRSIGRecord sig) {
*/
public void init(Properties config) {
hasGost = Security.getProviders("MessageDigest.GOST3411") != null;
hasEd25519 = Security.getProviders("KeyFactory.Ed25519") != null;
hasEd448 = Security.getProviders("KeyFactory.Ed448") != null;
this.config = config;
String dp = config.getProperty(DIGEST_PREFERENCE);
if (dp != null) {
Expand Down Expand Up @@ -871,21 +878,13 @@ boolean isAlgorithmSupported(int alg) {
case Algorithm.RSASHA512:
case Algorithm.ECDSAP256SHA256:
case Algorithm.ECDSAP384SHA384:
if (config == null) {
return true;
}

return Boolean.parseBoolean(config.getProperty(configKey, Boolean.TRUE.toString()));
return propertyOrTrueWithPrecondition(configKey, true);
case Algorithm.ECC_GOST:
if (!hasGost) {
return false;
}

if (config == null) {
return true;
}

return Boolean.parseBoolean(config.getProperty(configKey, Boolean.TRUE.toString()));
return propertyOrTrueWithPrecondition(configKey, hasGost);
case Algorithm.ED25519:
return propertyOrTrueWithPrecondition(configKey, hasEd25519);
case Algorithm.ED448:
return propertyOrTrueWithPrecondition(configKey, hasEd448);
default:
return false;
}
Expand Down Expand Up @@ -927,17 +926,21 @@ boolean isDigestSupported(int digestID) {

return Boolean.parseBoolean(config.getProperty(configKey, Boolean.TRUE.toString()));
case Digest.GOST3411:
if (!hasGost) {
return false;
}

if (config == null) {
return true;
}

return Boolean.parseBoolean(config.getProperty(configKey, Boolean.TRUE.toString()));
return propertyOrTrueWithPrecondition(configKey, hasGost);
default:
return false;
}
}

private boolean propertyOrTrueWithPrecondition(String configKey, boolean precondition) {
if (!precondition) {
return false;
}

if (config == null) {
return true;
}

return Boolean.parseBoolean(config.getProperty(configKey, Boolean.TRUE.toString()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,11 @@ public void setTSIGKey(TSIG key) {
this.headResolver.setTSIGKey(key);
}

@Override
public Duration getTimeout() {
return this.headResolver.getTimeout();
}

@Override
public void setTimeout(Duration duration) {
this.headResolver.setTimeout(duration);
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/org/jitsi/dnssec/TestAlgorithmSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.security.Security;
import java.util.Properties;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jitsi.dnssec.validator.ValUtils;
import org.junit.Test;
import org.powermock.reflect.Whitebox;
Expand Down Expand Up @@ -44,6 +48,30 @@ public void testEccgostAlgIsUnknown() throws IOException {
assertEquals("insecure.ds.noalgorithms:eccgost.ingotronic.ch.", getReason(response));
}

@Test
public void testEd25519() throws IOException {
BouncyCastleProvider bc = new BouncyCastleProvider();
Security.addProvider(bc);
resolver.init(new Properties());
Message response = resolver.send(createMessage("ed25519.nl./A"));
assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NOERROR, response.getRcode());
assertNull(getReason(response));
Security.removeProvider(bc.getName());
}

@Test
public void testEd448() throws IOException {
BouncyCastleProvider bc = new BouncyCastleProvider();
Security.addProvider(bc);
resolver.init(new Properties());
Message response = resolver.send(createMessage("ed448.nl./A"));
assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD));
assertEquals(Rcode.NOERROR, response.getRcode());
assertNull(getReason(response));
Security.removeProvider(bc.getName());
}

@Test
public void testDigestIdIsUnknown() throws IOException {
Message response = resolver.send(createMessage("unknown-alg.ingotronic.ch./A"));
Expand Down
19 changes: 10 additions & 9 deletions src/test/java/org/jitsi/dnssec/TestBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import org.jitsi.dnssec.validator.ValidatingResolver;
import org.junit.Assert;
import org.junit.Before;
Expand Down Expand Up @@ -101,6 +102,7 @@ protected void starting(Description description) {
"/recordings/" + description.getClassName().replace(".", "_") + "/" + testName;
File f = new File("./src/test/resources" + filename);
if ((record || !f.exists()) && !alwaysOffline) {
resolverClock = Clock.systemUTC();
f.getParentFile().getParentFile().mkdir();
f.getParentFile().mkdir();
w = new FileWriter(f.getAbsoluteFile());
Expand Down Expand Up @@ -136,7 +138,7 @@ protected void starting(Description description) {
@Override
protected void finished(Description description) {
try {
if (record && w != null) {
if (w != null) {
w.flush();
w.close();
w = null;
Expand All @@ -157,7 +159,7 @@ public static void setupClass() {
public void setup() throws NumberFormatException, IOException, DNSSECException {
resolver =
new ValidatingResolver(
new SimpleResolver("62.192.5.131") {
new SimpleResolver("8.8.4.4") {
@Override
public CompletionStage<Message> sendAsync(Message query) {
logger.info("---{}", key(query));
Expand All @@ -168,16 +170,17 @@ public CompletionStage<Message> sendAsync(Message query) {
Assert.fail("Response for " + key(query) + " not found.");
}

Message networkResult = null;
Message networkResult;
try {
networkResult = super.send(query);
if (record) {
networkResult = super.sendAsync(query).toCompletableFuture().get();
if (w != null) {
w.write(networkResult.toString());
w.write("\n\n###############################################\n\n");
}
} catch (IOException e) {
} catch (IOException | InterruptedException | ExecutionException e) {
CompletableFuture<Message> f = new CompletableFuture<>();
f.completeExceptionally(e);
return f;
}

return CompletableFuture.completedFuture(networkResult);
Expand Down Expand Up @@ -206,9 +209,7 @@ protected void add(String query, Message response, boolean clear) throws IOExcep

try {
setup();
} catch (NumberFormatException e) {
throw new IOException(e);
} catch (DNSSECException e) {
} catch (NumberFormatException | DNSSECException e) {
throw new IOException(e);
}
}
Expand Down
55 changes: 46 additions & 9 deletions src/test/java/org/jitsi/dnssec/TestPriming.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.powermock.api.mockito.PowerMockito.doAnswer;
import static org.powermock.api.mockito.PowerMockito.doReturn;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.whenNew;

import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
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.DClass;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
Expand All @@ -38,7 +41,7 @@
import org.xbill.DNS.Type;

@RunWith(PowerMockRunner.class)
@PrepareForTest(DNSKEYRecord.class)
@PrepareForTest({Record.class, DNSKEYRecord.class})
public class TestPriming extends TestBase {
@Test
public void testDnskeyPrimeResponseWithEmptyAnswerIsBad() throws IOException {
Expand Down Expand Up @@ -91,9 +94,26 @@ public void testDnskeyPrimeResponseWithMismatchedFootprintIsBad() throws Excepti
}

public void prepareTestDnskeyPrimeResponseWithMismatchedFootprintIsBad() throws Exception {
DNSKEYRecord emptyDnskeyRecord = spy(Whitebox.invokeConstructor(DNSKEYRecord.class));
when(emptyDnskeyRecord.getFootprint()).thenReturn(-1);
whenNew(DNSKEYRecord.class).withNoArguments().thenReturn(emptyDnskeyRecord);
spy(Record.class);
doAnswer(
(Answer<Record>)
getEmptyRecordInvocation -> {
Record orig = (Record) getEmptyRecordInvocation.callRealMethod();
if (orig instanceof DNSKEYRecord) {
DNSKEYRecord dr = spy((DNSKEYRecord) orig);
when(dr.getFootprint()).thenReturn(-1);
return dr;
}
return orig;
})
.when(
Record.class,
"getEmptyRecord",
any(),
eq(Type.DNSKEY),
eq(DClass.IN),
anyLong(),
anyBoolean());
}

@Test
Expand All @@ -107,9 +127,26 @@ public void testDnskeyPrimeResponseWithMismatchedAlgorithmIsBad()
}

public void prepareTestDnskeyPrimeResponseWithMismatchedAlgorithmIsBad() throws Exception {
DNSKEYRecord emptyDnskeyRecord = spy(Whitebox.invokeConstructor(DNSKEYRecord.class));
when(emptyDnskeyRecord.getAlgorithm()).thenReturn(-1);
whenNew(DNSKEYRecord.class).withNoArguments().thenReturn(emptyDnskeyRecord);
spy(Record.class);
doAnswer(
(Answer<Record>)
getEmptyRecordInvocation -> {
Record orig = (Record) getEmptyRecordInvocation.callRealMethod();
if (orig instanceof DNSKEYRecord) {
DNSKEYRecord dr = spy((DNSKEYRecord) orig);
when(dr.getAlgorithm()).thenReturn(-1);
return dr;
}
return orig;
})
.when(
Record.class,
"getEmptyRecord",
any(),
eq(Type.DNSKEY),
eq(DClass.IN),
anyLong(),
anyBoolean());
}

@Test
Expand Down
Loading

0 comments on commit ab53e6a

Please sign in to comment.