Skip to content
Permalink
Browse files

Add support for EdDSA in DNSSEC (RFC 8080)

  • Loading branch information
ibauersachs committed Jan 19, 2020
1 parent 500df98 commit afccc50b7803b2d38172440215f817362b741390
@@ -240,8 +240,8 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.61</version>
<scope>test</scope>
<version>1.64</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
@@ -72,6 +72,12 @@ private Algorithm() {}
/** ECDSA Curve P-384 with SHA-384 public key * */
public static final int ECDSAP384SHA384 = 14;

/** Edwards-Curve Digital Security Algorithm (EdDSA) for DNSSEC, RFC8080 */
public static final int ED25519 = 15;

/** Edwards-Curve Digital Security Algorithm (EdDSA) for DNSSEC, RFC8080 */
public static final int ED448 = 16;

/** Indirect keys; the actual key is elsewhere. */
public static final int INDIRECT = 252;

@@ -98,6 +104,8 @@ private Algorithm() {}
algs.add(ECC_GOST, "ECC-GOST");
algs.add(ECDSAP256SHA256, "ECDSAP256SHA256");
algs.add(ECDSAP384SHA384, "ECDSAP384SHA384");
algs.add(ED25519, "ED25519");
algs.add(ED448, "ED448");
algs.add(INDIRECT, "INDIRECT");
algs.add(PRIVATEDNS, "PRIVATEDNS");
algs.add(PRIVATEOID, "PRIVATEOID");
@@ -506,6 +514,7 @@ private static PublicKey toECDSAPublicKey(byte[] key, ECKeyInfo keyinfo)
KeyFactory factory = KeyFactory.getInstance("EC");
return factory.generatePublic(new ECPublicKeySpec(q, keyinfo.spec));
}

/** Converts a KEY/DNSKEY record into a PublicKey */
static PublicKey toPublicKey(KEYBase r) throws DNSSECException {
return toPublicKey(r.getAlgorithm(), r.getKey(), r);
@@ -530,6 +539,9 @@ static PublicKey toPublicKey(int alg, byte[] key, Record r) throws DNSSECExcepti
return toECDSAPublicKey(key, ECDSA_P256);
case Algorithm.ECDSAP384SHA384:
return toECDSAPublicKey(key, ECDSA_P384);
case Algorithm.ED25519:
case Algorithm.ED448:
return DNSSECWithBC.toPublicKey(alg, key);
default:
throw new UnsupportedAlgorithmException(alg);
}
@@ -632,6 +644,9 @@ static PublicKey toPublicKey(int alg, byte[] key, Record r) throws DNSSECExcepti
throw new IncompatibleKeyException();
}
return fromECDSAPublicKey((ECPublicKey) key, ECDSA_P384);
case Algorithm.ED25519:
case Algorithm.ED448:
return DNSSECWithBC.fromPublicKey(key, alg);
default:
throw new UnsupportedAlgorithmException(alg);
}
@@ -663,6 +678,9 @@ public static String algString(int alg) throws UnsupportedAlgorithmException {
return "SHA256withECDSA";
case Algorithm.ECDSAP384SHA384:
return "SHA384withECDSA";
case Algorithm.ED25519:
case Algorithm.ED448:
return "EdDSA";
default:
throw new UnsupportedAlgorithmException(alg);
}
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: BSD-2-Clause
package org.xbill.DNS;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey;
import org.xbill.DNS.DNSSEC.Algorithm;
import org.xbill.DNS.DNSSEC.DNSSECException;
import org.xbill.DNS.DNSSEC.IncompatibleKeyException;
import org.xbill.DNS.DNSSEC.UnsupportedAlgorithmException;

/**
* Utility class for EdDSA signatures. Keep separate from {@link DNSSEC} to keep BouncyCastle
* optional until JEP 339 is available in Java.
*/
class DNSSECWithBC {
static PublicKey toPublicKey(int alg, byte[] key)
throws DNSSECException, GeneralSecurityException, IOException {
switch (alg) {
case Algorithm.ED25519:
return toEdDSAPublicKey(key, EdECObjectIdentifiers.id_Ed25519);
case Algorithm.ED448:
return toEdDSAPublicKey(key, EdECObjectIdentifiers.id_Ed448);
default:
throw new UnsupportedAlgorithmException(alg);
}
}

static byte[] fromPublicKey(PublicKey key, int alg) throws DNSSECException {
switch (alg) {
case Algorithm.ED25519:
case Algorithm.ED448:
if (!(key instanceof BCEdDSAPublicKey) || !key.getFormat().equalsIgnoreCase("X.509")) {
throw new IncompatibleKeyException();
}
return fromEdDSAPublicKey(key);
default:
throw new UnsupportedAlgorithmException(alg);
}
}

private static PublicKey toEdDSAPublicKey(byte[] key, ASN1ObjectIdentifier algId)
throws GeneralSecurityException, IOException {
// Key is encoded as plain octets, rfc8080#section-3
// wrap it in ASN.1 format so we can use X509EncodedKeySpec to read it as JCA
SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(algId), key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyInfo.getEncoded());

KeyFactory keyFactory = KeyFactory.getInstance("EdDSA");
return keyFactory.generatePublic(keySpec);
}

private static byte[] fromEdDSAPublicKey(PublicKey key) {
DNSOutput out = new DNSOutput();
byte[] encoded = key.getEncoded();
// subtract the X.509 prefix length
out.writeByteArray(encoded, 12, encoded.length - 12);
return out.toByteArray();
}
}
@@ -1,23 +1,30 @@
package org.xbill.DNS;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.Signature;
import java.time.Instant;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.xbill.DNS.DNSSEC.Algorithm;
import org.xbill.DNS.DNSSEC.DNSSECException;

public class DNSSECWithBCProviderTest {

private static final String KEY_ALGORITHM = "RSA";
int algorithm = Algorithm.RSASHA1;
String bcJCAProvider = "BC";
byte[] toSign = "The quick brown fox jumped over the lazy dog.".getBytes();
private Name exampleCom = Name.fromConstantString("example.com.");

@BeforeAll
static void setUp() {
@@ -42,4 +49,89 @@ void testSignWithDNSSECAndBCProvider() throws Exception {
boolean verify = verifier.verify(signature);
assertTrue(verify);
}

@Test
void testEdDSA25519_DNSKEY() throws IOException, DNSSECException {
String rrString = "257 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=";
DNSKEYRecord dnskey =
(DNSKEYRecord)
Record.fromString(exampleCom, Type.DNSKEY, DClass.IN, 3600, rrString, Name.root);
assertEquals(Algorithm.ED25519, dnskey.getAlgorithm());
assertEquals("Ed25519", dnskey.getPublicKey().getAlgorithm());
DNSKEYRecord dnskey2 =
new DNSKEYRecord(exampleCom, DClass.IN, 3600, 257, 3, 15, dnskey.getPublicKey());
assertEquals(rrString, dnskey2.rrToString());
}

@Test
void testEdDSA25519_DS() throws IOException {
DSRecord ds =
(DSRecord)
Record.fromString(
exampleCom,
Type.DS,
DClass.IN,
3600,
"3613 15 2 3aa5ab37efce57f737fc1627013fee07bdf241bd10f3b1964ab55c78e79a304b",
Name.root);
assertEquals(Algorithm.ED25519, ds.getAlgorithm());
}

@Test
void testEdDSA448_DNSKEY() throws IOException, DNSSECException {
String rrString =
"257 3 16 3kgROaDjrh0H2iuixWBrc8g2EpBBLCdGzHmn+G2MpTPhpj/OiBVHHSfPodx1FYYUcJKm1MDpJtIA";
DNSKEYRecord dnskey =
(DNSKEYRecord)
Record.fromString(exampleCom, Type.DNSKEY, DClass.IN, 3600, rrString, Name.root);
assertEquals(Algorithm.ED448, dnskey.getAlgorithm());
assertEquals("Ed448", dnskey.getPublicKey().getAlgorithm());
DNSKEYRecord dnskey2 =
new DNSKEYRecord(exampleCom, DClass.IN, 3600, 257, 3, 16, dnskey.getPublicKey());
assertEquals(rrString, dnskey2.rrToString());
}

@Test
void testEdDSA448_DS() throws IOException {
DSRecord ds =
(DSRecord)
Record.fromString(
exampleCom,
Type.DS,
DClass.IN,
3600,
"9713 16 2 6ccf18d5bc5d7fc2fceb1d59d17321402f2aa8d368048db93dd811f5cb2b19c7",
Name.root);
assertEquals(Algorithm.ED448, ds.getAlgorithm());
}

@Test
void testEdDSA25519_verify() throws IOException, DNSSECException {
DNSKEYRecord dnskey =
(DNSKEYRecord)
Record.fromString(
exampleCom,
Type.DNSKEY,
DClass.IN,
3600,
"257 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=",
Name.root);

try (Master m =
new Master(
new ByteArrayInputStream(
("example.com. 3600 IN MX 10 mail.example.com.\n"
+ "example.com. 3600 IN RRSIG MX 15 2 3600 (\n"
+ " 1440021600 1438207200 3613 example.com. (\n"
+ " oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeRAvTdszaPD+QLs3f\n"
+ " x8A4M3e23mRZ9VrbpMngwcrqNAg== )")
.getBytes(StandardCharsets.US_ASCII)))) {
RRset set = new RRset();
Record r;
while ((r = m.nextRecord()) != null) {
set.addRR(r);
}
DNSSEC.verify(set, set.sigs().get(0), dnskey, Instant.parse("2015-08-19T22:00:00Z"));
}
}
}

0 comments on commit afccc50

Please sign in to comment.
You can’t perform that action at this time.