From ae803e4719d176c32d8c35adc3c7e9b9dbe5d17c Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 16 Oct 2024 09:35:53 +0200 Subject: [PATCH] Switch to external MiniDNS again --- app/build.gradle | 2 +- .../dane/DaneCertificateException.java | 65 -- .../java/org/minidns/dane/DaneVerifier.java | 280 --------- .../minidns/dane/ExpectingTrustManager.java | 63 -- .../minidns/dane/X509TrustManagerUtil.java | 44 -- .../org/minidns/dnssec/DigestCalculator.java | 15 - .../java/org/minidns/dnssec/DnssecClient.java | 573 ------------------ .../org/minidns/dnssec/DnssecQueryResult.java | 53 -- .../DnssecResultNotAuthenticException.java | 48 -- .../dnssec/DnssecUnverifiedReason.java | 175 ------ .../DnssecValidationFailedException.java | 104 ---- ...nssecValidatorInitializationException.java | 19 - .../org/minidns/dnssec/SignatureVerifier.java | 18 - .../java/org/minidns/dnssec/Verifier.java | 219 ------- .../dnssec/algorithms/AlgorithmMap.java | 121 ---- .../algorithms/DsaSignatureVerifier.java | 132 ---- .../algorithms/EcdsaSignatureVerifier.java | 133 ---- .../algorithms/EcgostSignatureVerifier.java | 86 --- .../algorithms/JavaSecDigestCalculator.java | 29 - .../algorithms/JavaSecSignatureVerifier.java | 62 -- .../algorithms/RsaSignatureVerifier.java | 67 -- 21 files changed, 1 insertion(+), 2307 deletions(-) delete mode 100644 app/src/main/java/org/minidns/dane/DaneCertificateException.java delete mode 100644 app/src/main/java/org/minidns/dane/DaneVerifier.java delete mode 100644 app/src/main/java/org/minidns/dane/ExpectingTrustManager.java delete mode 100644 app/src/main/java/org/minidns/dane/X509TrustManagerUtil.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DigestCalculator.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DnssecClient.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DnssecQueryResult.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DnssecResultNotAuthenticException.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DnssecUnverifiedReason.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DnssecValidationFailedException.java delete mode 100644 app/src/main/java/org/minidns/dnssec/DnssecValidatorInitializationException.java delete mode 100644 app/src/main/java/org/minidns/dnssec/SignatureVerifier.java delete mode 100644 app/src/main/java/org/minidns/dnssec/Verifier.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/AlgorithmMap.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/DsaSignatureVerifier.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/EcdsaSignatureVerifier.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/EcgostSignatureVerifier.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/JavaSecDigestCalculator.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/JavaSecSignatureVerifier.java delete mode 100644 app/src/main/java/org/minidns/dnssec/algorithms/RsaSignatureVerifier.java diff --git a/app/build.gradle b/app/build.gradle index 6929624363..84e3ba2a53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -524,7 +524,7 @@ configurations.configureEach { exclude group: "com.atlassian.commonmark", module: "commonmark" // https://github.com/MiniDNS/minidns/issues/139 - exclude group: "org.minidns", module: "minidns-dnssec" + //exclude group: "org.minidns", module: "minidns-dnssec" } configurations.configureEach { diff --git a/app/src/main/java/org/minidns/dane/DaneCertificateException.java b/app/src/main/java/org/minidns/dane/DaneCertificateException.java deleted file mode 100644 index 3fd4bef550..0000000000 --- a/app/src/main/java/org/minidns/dane/DaneCertificateException.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dane; - -import java.security.cert.CertificateException; -import java.util.Collections; -import java.util.List; - -import org.minidns.record.TLSA; - -public abstract class DaneCertificateException extends CertificateException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - protected DaneCertificateException() { - } - - protected DaneCertificateException(String message) { - super(message); - } - - public static class CertificateMismatch extends DaneCertificateException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - public final TLSA tlsa; - public final byte[] computed; - - public CertificateMismatch(TLSA tlsa, byte[] computed) { - super("The TLSA RR does not match the certificate"); - this.tlsa = tlsa; - this.computed = computed; - } - } - - public static class MultipleCertificateMismatchExceptions extends DaneCertificateException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - public final List certificateMismatchExceptions; - - public MultipleCertificateMismatchExceptions(List certificateMismatchExceptions) { - super("There where multiple CertificateMismatch exceptions because none of the TLSA RR does match the certificate"); - assert !certificateMismatchExceptions.isEmpty(); - this.certificateMismatchExceptions = Collections.unmodifiableList(certificateMismatchExceptions); - } - } -} diff --git a/app/src/main/java/org/minidns/dane/DaneVerifier.java b/app/src/main/java/org/minidns/dane/DaneVerifier.java deleted file mode 100644 index 7d57da802a..0000000000 --- a/app/src/main/java/org/minidns/dane/DaneVerifier.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dane; - -import org.minidns.dnsmessage.DnsMessage; -import org.minidns.dnsname.DnsName; -import org.minidns.dnssec.DnssecClient; -import org.minidns.dnssec.DnssecQueryResult; -import org.minidns.dnssec.DnssecUnverifiedReason; -import org.minidns.record.Data; -import org.minidns.record.Record; -import org.minidns.record.CNAME; -import org.minidns.record.TLSA; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.logging.Logger; - -/** - * A helper class to validate the usage of TLSA records. - */ -public class DaneVerifier { - private static final Logger LOGGER = Logger.getLogger(DaneVerifier.class.getName()); - - private final DnssecClient client; - - public DaneVerifier() { - this(new DnssecClient()); - } - - public DaneVerifier(DnssecClient client) { - this.client = client; - } - - /** - * Verifies the certificate chain in an active {@link SSLSocket}. The socket must be connected. - * - * @param socket A connected {@link SSLSocket} whose certificate chain shall be verified using DANE. - * @return Whether the DANE verification is the only requirement according to the TLSA record. - * If this method returns {@code false}, additional PKIX validation is required. - * @throws CertificateException if the certificate chain provided differs from the one enforced using DANE. - */ - public boolean verify(SSLSocket socket) throws CertificateException { - if (!socket.isConnected()) { - throw new IllegalStateException("Socket not yet connected."); - } - return verify(socket.getSession()); - } - - /** - * Verifies the certificate chain in an active {@link SSLSession}. - * - * @param session An active {@link SSLSession} whose certificate chain shall be verified using DANE. - * @return Whether the DANE verification is the only requirement according to the TLSA record. - * If this method returns {@code false}, additional PKIX validation is required. - * @throws CertificateException if the certificate chain provided differs from the one enforced using DANE. - */ - public boolean verify(SSLSession session) throws CertificateException { - try { - return verifyCertificateChain(convert(session.getPeerCertificates()), session.getPeerHost(), session.getPeerPort()); - } catch (SSLPeerUnverifiedException e) { - throw new CertificateException("Peer not verified", e); - } - } - - /** - * Verifies a certificate chain to be valid when used with the given connection details using DANE. - * - * @param chain A certificate chain that should be verified using DANE. - * @param hostName The DNS name of the host this certificate chain belongs to. - * @param port The port number that was used to reach the server providing the certificate chain in question. - * @return Whether the DANE verification is the only requirement according to the TLSA record. - * If this method returns {@code false}, additional PKIX validation is required. - * @throws CertificateException if the certificate chain provided differs from the one enforced using DANE. - */ - public boolean verifyCertificateChain(X509Certificate[] chain, String hostName, int port) throws CertificateException { - DnsName req = DnsName.from("_" + port + "._tcp." + hostName); - DnssecQueryResult result; - try { - result = client.queryDnssec(req, Record.TYPE.TLSA); - } catch (IOException e) { - throw new RuntimeException(e); - } - DnsMessage res = result.dnsQueryResult.response; - // TODO: We previously used the AD bit here. This allowed non-DNSSEC aware clients to be plugged into - // DaneVerifier, which, in turn, allows to use a trusted forward as DNSSEC validator. Is this a good idea? - if (!result.isAuthenticData()) { - String msg = "Got TLSA response from DNS server, but was not signed properly."; - msg += " Reasons:"; - for (DnssecUnverifiedReason reason : result.getUnverifiedReasons()) { - msg += " " + reason; - } - LOGGER.info(msg); - return false; - } - - List certificateMismatchExceptions = new LinkedList<>(); - boolean verified = false; - for (Record record : res.answerSection) { - if (record.name.equals(req)) { - if (record.type == Record.TYPE.TLSA) { - TLSA tlsa = (TLSA) record.payloadData; - try { - verified |= checkCertificateMatches(chain[0], tlsa, hostName); - } catch (DaneCertificateException.CertificateMismatch certificateMismatchException) { - // Record the mismatch and only throw an exception if no - // TLSA RR is able to verify the cert. This allows for TLSA - // certificate rollover. - certificateMismatchExceptions.add(certificateMismatchException); - } - if (verified) break; - } - // https://github.com/MiniDNS/minidns/issues/140 - else if (record.type == Record.TYPE.CNAME) { - req = ((CNAME) record.payloadData).target; - } - } - } - - if (!verified && !certificateMismatchExceptions.isEmpty()) { - throw new DaneCertificateException.MultipleCertificateMismatchExceptions(certificateMismatchExceptions); - } - - return verified; - } - - 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; - } - - switch (tlsa.certUsage) { - case serviceCertificateConstraint: // PKIX-EE - case domainIssuedCertificate: // DANE-EE - break; - case caConstraint: // PKIX-TA - case trustAnchorAssertion: // DANE-TA - default: - LOGGER.warning("TLSA certificate usage " + tlsa.certUsage + " (" + tlsa.certUsageByte + ") not supported while verifying " + hostName); - return false; - } - - if (tlsa.selector == null) { - LOGGER.warning("TLSA selector byte " + tlsa.selectorByte + " is not supported while verifying " + hostName); - return false; - } - - byte[] comp = null; - switch (tlsa.selector) { - case fullCertificate: - comp = cert.getEncoded(); - break; - case subjectPublicKeyInfo: - comp = cert.getPublicKey().getEncoded(); - break; - default: - LOGGER.warning("TLSA selector " + tlsa.selector + " (" + tlsa.selectorByte + ") not supported while verifying " + hostName); - return false; - } - - if (tlsa.matchingType == null) { - LOGGER.warning("TLSA matching type byte " + tlsa.matchingTypeByte + " is not supported while verifying " + hostName); - return false; - } - - switch (tlsa.matchingType) { - case noHash: - break; - case sha256: - try { - comp = MessageDigest.getInstance("SHA-256").digest(comp); - } catch (NoSuchAlgorithmException e) { - throw new CertificateException("Verification using TLSA failed: could not SHA-256 for matching", e); - } - break; - case sha512: - try { - comp = MessageDigest.getInstance("SHA-512").digest(comp); - } catch (NoSuchAlgorithmException e) { - throw new CertificateException("Verification using TLSA failed: could not SHA-512 for matching", e); - } - break; - default: - LOGGER.warning("TLSA matching type " + tlsa.matchingType + " not supported while verifying " + hostName); - return false; - } - - boolean matches = tlsa.certificateAssociationEquals(comp); - if (!matches) { - throw new DaneCertificateException.CertificateMismatch(tlsa, comp); - } - - // domain issued certificate does not require further verification, - // service certificate constraint does. - return tlsa.certUsage == TLSA.CertUsage.domainIssuedCertificate; - } - - /** - * Invokes {@link HttpsURLConnection#connect()} in a DANE verified fashion. - * This method must be called before {@link HttpsURLConnection#connect()} is invoked. - * - * If a SSLSocketFactory was set on this HttpsURLConnection, it will be ignored. You can use - * {@link #verifiedConnect(HttpsURLConnection, X509TrustManager)} to inject a custom {@link TrustManager}. - * - * @param conn connection to be connected. - * @return The {@link HttpsURLConnection} after being connected. - * @throws IOException when the connection could not be established. - * @throws CertificateException if there was an exception while verifying the certificate. - */ - public HttpsURLConnection verifiedConnect(HttpsURLConnection conn) throws IOException, CertificateException { - return verifiedConnect(conn, null); - } - - /** - * Invokes {@link HttpsURLConnection#connect()} in a DANE verified fashion. - * This method must be called before {@link HttpsURLConnection#connect()} is invoked. - * - * If a SSLSocketFactory was set on this HttpsURLConnection, it will be ignored. - * - * @param conn connection to be connected. - * @param trustManager A non-default {@link TrustManager} to be used. - * @return The {@link HttpsURLConnection} after being connected. - * @throws IOException when the connection could not be established. - * @throws CertificateException if there was an exception while verifying the certificate. - */ - public HttpsURLConnection verifiedConnect(HttpsURLConnection conn, X509TrustManager trustManager) throws IOException, CertificateException { - try { - SSLContext context = SSLContext.getInstance("TLS"); - ExpectingTrustManager expectingTrustManager = new ExpectingTrustManager(trustManager); - context.init(null, new TrustManager[] {expectingTrustManager}, null); - conn.setSSLSocketFactory(context.getSocketFactory()); - conn.connect(); - boolean fullyVerified = verifyCertificateChain(convert(conn.getServerCertificates()), conn.getURL().getHost(), - conn.getURL().getPort() < 0 ? conn.getURL().getDefaultPort() : conn.getURL().getPort()); - // If fullyVerified is true then it's the DANE verification performed by verifiyCertificateChain() is - // sufficient to verify the certificate and we ignore possible pending exceptions of ExpectingTrustManager. - if (!fullyVerified && expectingTrustManager.hasException()) { - throw new IOException("Peer verification failed using PKIX", expectingTrustManager.getException()); - } - return conn; - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new RuntimeException(e); - } - } - - private static X509Certificate[] convert(Certificate[] certificates) { - List certs = new ArrayList<>(); - for (Certificate certificate : certificates) { - if (certificate instanceof X509Certificate) { - certs.add((X509Certificate) certificate); - } - } - return certs.toArray(new X509Certificate[certs.size()]); - } -} diff --git a/app/src/main/java/org/minidns/dane/ExpectingTrustManager.java b/app/src/main/java/org/minidns/dane/ExpectingTrustManager.java deleted file mode 100644 index 699bafd9ba..0000000000 --- a/app/src/main/java/org/minidns/dane/ExpectingTrustManager.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dane; - -import javax.net.ssl.X509TrustManager; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -public class ExpectingTrustManager implements X509TrustManager { - private CertificateException exception; - private final X509TrustManager trustManager; - - /** - * Creates a new instance of ExpectingTrustManager. - * - * @param trustManager The {@link X509TrustManager} to be used for verification. - * {@code null} to use the system default. - */ - public ExpectingTrustManager(X509TrustManager trustManager) { - this.trustManager = trustManager == null ? X509TrustManagerUtil.getDefault() : trustManager; - } - - public boolean hasException() { - return exception != null; - } - - public CertificateException getException() { - CertificateException e = exception; - exception = null; - return e; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - try { - trustManager.checkClientTrusted(chain, authType); - } catch (CertificateException e) { - exception = e; - } - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - try { - trustManager.checkServerTrusted(chain, authType); - } catch (CertificateException e) { - exception = e; - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return trustManager.getAcceptedIssuers(); - } -} diff --git a/app/src/main/java/org/minidns/dane/X509TrustManagerUtil.java b/app/src/main/java/org/minidns/dane/X509TrustManagerUtil.java deleted file mode 100644 index d09a9cb8d8..0000000000 --- a/app/src/main/java/org/minidns/dane/X509TrustManagerUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dane; - -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; - -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -public class X509TrustManagerUtil { - - public static X509TrustManager getDefault() { - return getDefault(null); - } - - public static X509TrustManager getDefault(KeyStore keyStore) { - String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - TrustManagerFactory trustManagerFactory; - try { - trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm); - trustManagerFactory.init(keyStore); - } catch (NoSuchAlgorithmException | KeyStoreException e) { - throw new AssertionError(e); - } - - for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { - if (trustManager instanceof X509TrustManager) { - return (X509TrustManager) trustManager; - } - } - throw new AssertionError("No trust manager for the default algorithm " + defaultAlgorithm + " found"); - } -} diff --git a/app/src/main/java/org/minidns/dnssec/DigestCalculator.java b/app/src/main/java/org/minidns/dnssec/DigestCalculator.java deleted file mode 100644 index af3ad42189..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DigestCalculator.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -public interface DigestCalculator { - byte[] digest(byte[] bytes); -} diff --git a/app/src/main/java/org/minidns/dnssec/DnssecClient.java b/app/src/main/java/org/minidns/dnssec/DnssecClient.java deleted file mode 100644 index 216541cfa9..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DnssecClient.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import org.minidns.DnsCache; -import org.minidns.dnsmessage.DnsMessage; -import org.minidns.dnsmessage.Question; -import org.minidns.dnsname.DnsName; -import org.minidns.dnsqueryresult.DnsQueryResult; -import org.minidns.dnssec.DnssecUnverifiedReason.NoActiveSignaturesReason; -import org.minidns.dnssec.DnssecUnverifiedReason.NoSecureEntryPointReason; -import org.minidns.dnssec.DnssecUnverifiedReason.NoSignaturesReason; -import org.minidns.dnssec.DnssecUnverifiedReason.NoTrustAnchorReason; -import org.minidns.dnssec.DnssecValidationFailedException.AuthorityDoesNotContainSoa; -import org.minidns.iterative.ReliableDnsClient; -import org.minidns.record.DLV; -import org.minidns.record.DNSKEY; -import org.minidns.record.DS; -import org.minidns.record.Data; -import org.minidns.record.DelegatingDnssecRR; -import org.minidns.record.NSEC; -import org.minidns.record.NSEC3; -import org.minidns.record.RRSIG; -import org.minidns.record.Record; -import org.minidns.record.Record.CLASS; -import org.minidns.record.Record.TYPE; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class DnssecClient extends ReliableDnsClient { - - /** - * The root zone's KSK. - * The ID of the current key is "Klajeyz", and the key tag value is "20326". - */ - private static final BigInteger rootEntryKey = new BigInteger("1628686155461064465348252249725010996177649738666492500572664444461532807739744536029771810659241049343994038053541290419968870563183856865780916376571550372513476957870843322273120879361960335192976656756972171258658400305760429696147778001233984421619267530978084631948434496468785021389956803104620471232008587410372348519229650742022804219634190734272506220018657920136902014393834092648785514548876370028925405557661759399901378816916683122474038734912535425670533237815676134840739565610963796427401855723026687073600445461090736240030247906095053875491225879656640052743394090544036297390104110989318819106653199917493"); - - private static final DnsName DEFAULT_DLV = DnsName.from("dlv.isc.org"); - - /** - * Create a new DNSSEC aware DNS client using the global default cache. - */ - public DnssecClient() { - this(DEFAULT_CACHE); - } - - /** - * Create a new DNSSEC aware DNS client with the given DNS cache. - * - * @param cache The backend DNS cache. - */ - public DnssecClient(DnsCache cache) { - super(cache); - addSecureEntryPoint(DnsName.ROOT, rootEntryKey.toByteArray()); - } - - /** - * Known secure entry points (SEPs). - */ - private final Map knownSeps = new ConcurrentHashMap<>(); - - private boolean stripSignatureRecords = true; - - /** - * The active DNSSEC Look-aside Validation Registry. May be null. - */ - private DnsName dlv; - - @Override - public DnsQueryResult query(Question q) throws IOException { - DnssecQueryResult dnssecQueryResult = queryDnssec(q); - if (!dnssecQueryResult.isAuthenticData()) { - // TODO: Refine exception. - throw new IOException(); - } - return dnssecQueryResult.dnsQueryResult; - } - - public DnssecQueryResult queryDnssec(CharSequence name, TYPE type) throws IOException { - Question q = new Question(name, type, CLASS.IN); - return queryDnssec(q); - } - - public DnssecQueryResult queryDnssec(Question q) throws IOException { - DnsQueryResult dnsQueryResult = super.query(q); - DnssecQueryResult dnssecQueryResult = performVerification(dnsQueryResult); - return dnssecQueryResult; - } - - private DnssecQueryResult performVerification(DnsQueryResult dnsQueryResult) throws IOException { - if (dnsQueryResult == null) return null; - - DnsMessage dnsMessage = dnsQueryResult.response; - DnsMessage.Builder messageBuilder = dnsMessage.asBuilder(); - - Set unverifiedReasons = verify(dnsMessage); - - messageBuilder.setAuthenticData(unverifiedReasons.isEmpty()); - - List> answers = dnsMessage.answerSection; - List> nameserverRecords = dnsMessage.authoritySection; - List> additionalResourceRecords = dnsMessage.additionalSection; - Set> signatures = new HashSet<>(); - Record.filter(signatures, RRSIG.class, answers); - Record.filter(signatures, RRSIG.class, nameserverRecords); - Record.filter(signatures, RRSIG.class, additionalResourceRecords); - - if (stripSignatureRecords) { - messageBuilder.setAnswers(stripSignatureRecords(answers)); - messageBuilder.setNameserverRecords(stripSignatureRecords(nameserverRecords)); - messageBuilder.setAdditionalResourceRecords(stripSignatureRecords(additionalResourceRecords)); - } - - return new DnssecQueryResult(messageBuilder.build(), dnsQueryResult, signatures, unverifiedReasons); - } - - private static List> stripSignatureRecords(List> records) { - if (records.isEmpty()) return records; - List> recordList = new ArrayList<>(records.size()); - for (Record record : records) { - if (record.type != TYPE.RRSIG) { - recordList.add(record); - } - } - return recordList; - } - - private Set verify(DnsMessage dnsMessage) throws IOException { - if (!dnsMessage.answerSection.isEmpty()) { - return verifyAnswer(dnsMessage); - } else { - return verifyNsec(dnsMessage); - } - } - - private Set verifyAnswer(DnsMessage dnsMessage) throws IOException { - Question q = dnsMessage.questions.get(0); - List> answers = dnsMessage.answerSection; - List> toBeVerified = dnsMessage.copyAnswers(); - VerifySignaturesResult verifiedSignatures = verifySignatures(q, answers, toBeVerified); - Set result = verifiedSignatures.reasons; - if (!result.isEmpty()) { - return result; - } - - // Keep SEPs separated, we only need one valid SEP. - boolean sepSignatureValid = false; - Set sepReasons = new HashSet<>(); - for (Iterator> iterator = toBeVerified.iterator(); iterator.hasNext(); ) { - Record record = iterator.next().ifPossibleAs(DNSKEY.class); - if (record == null) { - continue; - } - - // Verify all DNSKEYs as if it was a SEP. If we find a single SEP we are safe. - Set reasons = verifySecureEntryPoint(record); - if (reasons.isEmpty()) { - sepSignatureValid = true; - } else { - sepReasons.addAll(reasons); - } - if (!verifiedSignatures.sepSignaturePresent) { - LOGGER.finer("SEP key is not self-signed."); - } - iterator.remove(); - } - - if (verifiedSignatures.sepSignaturePresent && !sepSignatureValid) { - result.addAll(sepReasons); - } - if (verifiedSignatures.sepSignatureRequired && !verifiedSignatures.sepSignaturePresent) { - result.add(new NoSecureEntryPointReason(q.name)); - } - if (!toBeVerified.isEmpty()) { - if (toBeVerified.size() != answers.size()) { - throw new DnssecValidationFailedException(q, "Only some records are signed!"); - } else { - result.add(new NoSignaturesReason(q)); - } - } - return result; - } - - private Set verifyNsec(DnsMessage dnsMessage) throws IOException { - Set result = new HashSet<>(); - Question q = dnsMessage.questions.get(0); - boolean validNsec = false; - boolean nsecPresent = false; - - // Get the SOA RR that has to be in the authority section. Note that we will verify its signature later, after - // we have verified the NSEC3 RR. And although the data form the SOA RR is only required for NSEC3 we check for - // its existence here, since it would be invalid if there is none. - // TODO: Add a reference to the relevant RFC parts which specify that there has to be a SOA RR in X. - DnsName zone = null; - List> authoritySection = dnsMessage.authoritySection; - for (Record authorityRecord : authoritySection) { - if (authorityRecord.type == TYPE.SOA) { - zone = authorityRecord.name; - break; - } - } - if (zone == null) - throw new AuthorityDoesNotContainSoa(dnsMessage); - - // TODO Examine if it is better to verify the RRs in the authority section *before* we verify NSEC(3). We - // currently do it the other way around. - - // TODO: This whole logic needs to be changed. It currently checks one NSEC(3) record after another, when it - // should first determine if we are dealing with NSEC or NSEC3 and the verify the whole response. - for (Record record : authoritySection) { - DnssecUnverifiedReason reason; - - switch (record.type) { - case NSEC: - nsecPresent = true; - Record nsecRecord = record.as(NSEC.class); - reason = Verifier.verifyNsec(nsecRecord, q); - break; - case NSEC3: - nsecPresent = true; - Record nsec3Record = record.as(NSEC3.class); - reason = Verifier.verifyNsec3(zone, nsec3Record, q); - break; - default: - continue; - } - - if (reason != null) { - result.add(reason); - } else { - validNsec = true; - } - } - - // TODO: Shouldn't we also throw if !nsecPresent? - if (nsecPresent && !validNsec) { - throw new DnssecValidationFailedException(q, "Invalid NSEC!"); - } - - List> toBeVerified = dnsMessage.copyAuthority(); - VerifySignaturesResult verifiedSignatures = verifySignatures(q, authoritySection, toBeVerified); - if (validNsec && verifiedSignatures.reasons.isEmpty()) { - result.clear(); - } else { - result.addAll(verifiedSignatures.reasons); - } - - if (!toBeVerified.isEmpty() && toBeVerified.size() != authoritySection.size()) { - // TODO Refine this exception and include the missing toBeVerified RRs and the whole DnsMessage into it. - throw new DnssecValidationFailedException(q, "Only some resource records from the authority section are signed!"); - } - - return result; - } - - private static class VerifySignaturesResult { - boolean sepSignatureRequired = false; - boolean sepSignaturePresent = false; - Set reasons = new HashSet<>(); - } - - private VerifySignaturesResult verifySignatures(Question q, Collection> reference, List> toBeVerified) throws IOException { - final Date now = new Date(); - final List outdatedRrSigs = new LinkedList<>(); - VerifySignaturesResult result = new VerifySignaturesResult(); - final List> rrsigs = new ArrayList<>(toBeVerified.size()); - - for (Record recordToBeVerified : toBeVerified) { - Record record = recordToBeVerified.ifPossibleAs(RRSIG.class); - if (record == null) continue; - - RRSIG rrsig = record.payloadData; - if (rrsig.signatureExpiration.compareTo(now) < 0 || rrsig.signatureInception.compareTo(now) > 0) { - // This RRSIG is out of date, but there might be one that is not. - outdatedRrSigs.add(rrsig); - continue; - } - rrsigs.add(record); - } - - if (rrsigs.isEmpty()) { - if (!outdatedRrSigs.isEmpty()) { - result.reasons.add(new NoActiveSignaturesReason(q, outdatedRrSigs)); - } else { - // TODO: Check if QNAME results should have signatures and add a different reason if there are RRSIGs - // expected compared to when not. - result.reasons.add(new NoSignaturesReason(q)); - } - return result; - } - - for (Record sigRecord : rrsigs) { - RRSIG rrsig = sigRecord.payloadData; - - List> records = new ArrayList<>(reference.size()); - for (Record record : reference) { - if (record.type == rrsig.typeCovered && record.name.equals(sigRecord.name)) { - records.add(record); - } - } - - Set reasons = verifySignedRecords(q, rrsig, records); - result.reasons.addAll(reasons); - - if (q.name.equals(rrsig.signerName) && rrsig.typeCovered == TYPE.DNSKEY) { - for (Iterator> iterator = records.iterator(); iterator.hasNext(); ) { - Record dnsKeyRecord = iterator.next().ifPossibleAs(DNSKEY.class); - // dnsKeyRecord should never be null here. - DNSKEY dnskey = dnsKeyRecord.payloadData; - // DNSKEYs are verified separately, so don't mark them verified now. - iterator.remove(); - if (dnskey.getKeyTag() == rrsig.keyTag) { - result.sepSignaturePresent = true; - } - } - // DNSKEY's should be signed by a SEP - result.sepSignatureRequired = true; - } - - if (!isParentOrSelf(sigRecord.name.ace, rrsig.signerName.ace)) { - LOGGER.finer("Records at " + sigRecord.name + " are cross-signed with a key from " + rrsig.signerName); - } else { - toBeVerified.removeAll(records); - } - toBeVerified.remove(sigRecord); - } - return result; - } - - private static boolean isParentOrSelf(String child, String parent) { - if (child.equals(parent)) return true; - if (parent.isEmpty()) return true; - String[] childSplit = child.split("\\."); - String[] parentSplit = parent.split("\\."); - if (parentSplit.length > childSplit.length) return false; - for (int i = 1; i <= parentSplit.length; i++) { - if (!parentSplit[parentSplit.length - i].equals(childSplit[childSplit.length - i])) { - return false; - } - } - return true; - } - - private Set verifySignedRecords(Question q, RRSIG rrsig, List> records) throws IOException { - Set result = new HashSet<>(); - DNSKEY dnskey = null; - - if (rrsig.typeCovered == TYPE.DNSKEY) { - // Key must be present - List> dnskeyRrs = Record.filter(DNSKEY.class, records); - for (Record dnsKeyRecord : dnskeyRrs) { - if (dnsKeyRecord.payloadData.getKeyTag() == rrsig.keyTag) { - dnskey = dnsKeyRecord.payloadData; - break; - } - } - } else if (q.type == TYPE.DS && rrsig.signerName.equals(q.name)) { - // We should not probe for the self signed DS negative response, as it will be an endless loop. - result.add(new NoTrustAnchorReason(q.name)); - return result; - } else { - DnssecQueryResult dnskeyRes = queryDnssec(rrsig.signerName, TYPE.DNSKEY); - result.addAll(dnskeyRes.getUnverifiedReasons()); - List> dnskeyRrs = dnskeyRes.dnsQueryResult.response.filterAnswerSectionBy(DNSKEY.class); - for (Record dnsKeyRecord : dnskeyRrs) { - if (dnsKeyRecord.payloadData.getKeyTag() == rrsig.keyTag) { - dnskey = dnsKeyRecord.payloadData; - break; - } - } - } - - if (dnskey == null) { - throw new DnssecValidationFailedException(q, records.size() + " " + rrsig.typeCovered + " record(s) are signed using an unknown key."); - } - - DnssecUnverifiedReason unverifiedReason = Verifier.verify(records, rrsig, dnskey); - if (unverifiedReason != null) { - result.add(unverifiedReason); - } - - return result; - } - - private Set verifySecureEntryPoint(final Record sepRecord) throws IOException { - final DNSKEY dnskey = sepRecord.payloadData; - - Set unverifiedReasons = new HashSet<>(); - Set activeReasons = new HashSet<>(); - if (knownSeps.containsKey(sepRecord.name)) { - if (dnskey.keyEquals(knownSeps.get(sepRecord.name))) { - return unverifiedReasons; - } else { - unverifiedReasons.add(new DnssecUnverifiedReason.ConflictsWithSep(sepRecord)); - return unverifiedReasons; - } - } - - // If we are looking for the SEP of the root zone at this point, then the client was not - // configured with one and we can abort stating the reason. - if (sepRecord.name.isRootLabel()) { - unverifiedReasons.add(new DnssecUnverifiedReason.NoRootSecureEntryPointReason()); - return unverifiedReasons; - } - - DelegatingDnssecRR delegation = null; - DnssecQueryResult dsResp = queryDnssec(sepRecord.name, TYPE.DS); - unverifiedReasons.addAll(dsResp.getUnverifiedReasons()); - - List> dsRrs = dsResp.dnsQueryResult.response.filterAnswerSectionBy(DS.class); - for (Record dsRecord : dsRrs) { - DS ds = dsRecord.payloadData; - if (dnskey.getKeyTag() == ds.keyTag) { - delegation = ds; - activeReasons = dsResp.getUnverifiedReasons(); - break; - } - } - - if (delegation == null) { - LOGGER.fine("There is no DS record for " + sepRecord.name + ", server gives empty result"); - } - - if (delegation == null && dlv != null && !dlv.isChildOf(sepRecord.name)) { - DnssecQueryResult dlvResp = queryDnssec(DnsName.from(sepRecord.name, dlv), TYPE.DLV); - unverifiedReasons.addAll(dlvResp.getUnverifiedReasons()); - - List> dlvRrs = dlvResp.dnsQueryResult.response.filterAnswerSectionBy(DLV.class); - for (Record dlvRecord : dlvRrs) { - if (sepRecord.payloadData.getKeyTag() == dlvRecord.payloadData.keyTag) { - LOGGER.fine("Found DLV for " + sepRecord.name + ", awesome."); - delegation = dlvRecord.payloadData; - activeReasons = dlvResp.getUnverifiedReasons(); - break; - } - } - } - - if (delegation != null) { - DnssecUnverifiedReason unverifiedReason = Verifier.verify(sepRecord, delegation); - if (unverifiedReason != null) { - unverifiedReasons.add(unverifiedReason); - } else { - unverifiedReasons = activeReasons; - } - } else if (unverifiedReasons.isEmpty()) { - unverifiedReasons.add(new NoTrustAnchorReason(sepRecord.name)); - } - return unverifiedReasons; - } - - @Override - protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) { - message.getEdnsBuilder().setUdpPayloadSize(dataSource.getUdpPayloadSize()).setDnssecOk(); - message.setCheckingDisabled(true); - return super.newQuestion(message); - } - - @Override - protected String isResponseAcceptable(DnsMessage response) { - boolean dnssecOk = response.isDnssecOk(); - if (!dnssecOk) { - // This is a deliberate violation of RFC 6840 § 5.6. I doubt that - // "resolvers MUST ignore the DO bit in responses" does any good. Also we basically ignore the DO bit after - // the fall back to iterative mode. - return "DNSSEC OK (DO) flag not set in response"; - } - boolean checkingDisabled = response.checkingDisabled; - if (!checkingDisabled) { - return "CHECKING DISABLED (CD) flag not set in response"; - } - return super.isResponseAcceptable(response); - } - - /** - * Add a new secure entry point to the list of known secure entry points. - * - * A secure entry point acts as a trust anchor. By default, the only secure entry point is the key signing key - * provided by the root zone. - * - * @param name The domain name originating the key. Once the secure entry point for this domain is requested, - * the resolver will use this key without further verification instead of using the DNS system to - * verify the key. - * @param key The secure entry point corresponding to the domain name. This key can be retrieved by requesting - * the DNSKEY record for the domain and using the key with first flags bit set - * (also called key signing key) - */ - public void addSecureEntryPoint(DnsName name, byte[] key) { - knownSeps.put(name, key); - } - - /** - * Remove the secure entry point stored for a domain name. - * - * @param name The domain name of which the corresponding secure entry point shall be removed. For the root zone, - * use the empty string here. - */ - public void removeSecureEntryPoint(DnsName name) { - knownSeps.remove(name); - } - - /** - * Clears the list of known secure entry points. - * - * This will also remove the secure entry point of the root zone and - * thus render this instance useless until a new secure entry point is added. - */ - public void clearSecureEntryPoints() { - knownSeps.clear(); - } - - /** - * Whether signature records (RRSIG) are stripped from the resulting {@link DnsMessage}. - * - * Default is {@code true}. - * - * @return Whether signature records are stripped. - */ - public boolean isStripSignatureRecords() { - return stripSignatureRecords; - } - - /** - * Enable or disable stripping of signature records (RRSIG) from the result {@link DnsMessage}. - * @param stripSignatureRecords Whether signature records shall be stripped. - */ - public void setStripSignatureRecords(boolean stripSignatureRecords) { - this.stripSignatureRecords = stripSignatureRecords; - } - - /** - * Enables DNSSEC Lookaside Validation (DLV) using the default DLV service at dlv.isc.org. - */ - public void enableLookasideValidation() { - configureLookasideValidation(DEFAULT_DLV); - } - - /** - * Disables DNSSEC Lookaside Validation (DLV). - * DLV is disabled by default, this is only required if {@link #enableLookasideValidation()} was used before. - */ - public void disableLookasideValidation() { - configureLookasideValidation(null); - } - - /** - * Enables DNSSEC Lookaside Validation (DLV) using the given DLV service. - * - * @param dlv The domain name of the DLV service to be used or {@code null} to disable DLV. - */ - public void configureLookasideValidation(DnsName dlv) { - this.dlv = dlv; - } -} diff --git a/app/src/main/java/org/minidns/dnssec/DnssecQueryResult.java b/app/src/main/java/org/minidns/dnssec/DnssecQueryResult.java deleted file mode 100644 index 41a9993ab8..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DnssecQueryResult.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import java.util.Collections; -import java.util.Set; - -import org.minidns.dnsmessage.DnsMessage; -import org.minidns.dnsqueryresult.DnsQueryResult; -import org.minidns.record.RRSIG; -import org.minidns.record.Record; - -public class DnssecQueryResult { - - public final DnsMessage synthesizedResponse; - public final DnsQueryResult dnsQueryResult; - - private final Set> signatures; - private final Set dnssecUnverifiedReasons; - - DnssecQueryResult(DnsMessage synthesizedResponse, DnsQueryResult dnsQueryResult, Set> signatures, - Set dnssecUnverifiedReasons) { - this.synthesizedResponse = synthesizedResponse; - this.dnsQueryResult = dnsQueryResult; - this.signatures = Collections.unmodifiableSet(signatures); - if (dnssecUnverifiedReasons == null) { - this.dnssecUnverifiedReasons = Collections.emptySet(); - } else { - this.dnssecUnverifiedReasons = Collections.unmodifiableSet(dnssecUnverifiedReasons); - } - } - - public boolean isAuthenticData() { - return dnssecUnverifiedReasons.isEmpty(); - } - - public Set> getSignatures() { - return signatures; - } - - public Set getUnverifiedReasons() { - return dnssecUnverifiedReasons; - } - -} diff --git a/app/src/main/java/org/minidns/dnssec/DnssecResultNotAuthenticException.java b/app/src/main/java/org/minidns/dnssec/DnssecResultNotAuthenticException.java deleted file mode 100644 index b3c41b53e4..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DnssecResultNotAuthenticException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import java.util.Collections; -import java.util.Set; - -import org.minidns.MiniDnsException; - -public final class DnssecResultNotAuthenticException extends MiniDnsException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private final Set unverifiedReasons; - - private DnssecResultNotAuthenticException(String message, Set unverifiedReasons) { - super(message); - if (unverifiedReasons.isEmpty()) { - throw new IllegalArgumentException(); - } - this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons); - } - - public static DnssecResultNotAuthenticException from(Set unverifiedReasons) { - StringBuilder sb = new StringBuilder(); - sb.append("DNSSEC result not authentic. Reasons: "); - for (DnssecUnverifiedReason reason : unverifiedReasons) { - sb.append(reason).append('.'); - } - - return new DnssecResultNotAuthenticException(sb.toString(), unverifiedReasons); - } - - public Set getUnverifiedReasons() { - return unverifiedReasons; - } -} diff --git a/app/src/main/java/org/minidns/dnssec/DnssecUnverifiedReason.java b/app/src/main/java/org/minidns/dnssec/DnssecUnverifiedReason.java deleted file mode 100644 index 9f3df6221a..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DnssecUnverifiedReason.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import java.util.Collections; -import java.util.List; - -import org.minidns.constants.DnssecConstants.DigestAlgorithm; -import org.minidns.dnsmessage.Question; -import org.minidns.dnsname.DnsName; -import org.minidns.record.DNSKEY; -import org.minidns.record.Data; -import org.minidns.record.RRSIG; -import org.minidns.record.Record; -import org.minidns.record.Record.TYPE; - -public abstract class DnssecUnverifiedReason { - public abstract String getReasonString(); - - @Override - public String toString() { - return getReasonString(); - } - - @Override - public int hashCode() { - return getReasonString().hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof DnssecUnverifiedReason && ((DnssecUnverifiedReason) obj).getReasonString().equals(getReasonString()); - } - - public static class AlgorithmNotSupportedReason extends DnssecUnverifiedReason { - private final String algorithm; - private final TYPE type; - private final Record record; - - public AlgorithmNotSupportedReason(byte algorithm, TYPE type, Record record) { - this.algorithm = Integer.toString(algorithm & 0xff); - this.type = type; - this.record = record; - } - - @Override - public String getReasonString() { - return type.name() + " algorithm " + algorithm + " required to verify " + record.name + " is unknown or not supported by platform"; - } - } - - public static class AlgorithmExceptionThrownReason extends DnssecUnverifiedReason { - private final int algorithmNumber; - private final String kind; - private final Exception reason; - private final Record record; - - public AlgorithmExceptionThrownReason(DigestAlgorithm algorithm, String kind, Record record, Exception reason) { - this.algorithmNumber = algorithm.value; - this.kind = kind; - this.record = record; - this.reason = reason; - } - - @Override - public String getReasonString() { - return kind + " algorithm " + algorithmNumber + " threw exception while verifying " + record.name + ": " + reason; - } - } - - public static class ConflictsWithSep extends DnssecUnverifiedReason { - private final Record record; - - public ConflictsWithSep(Record record) { - this.record = record; - } - - @Override - public String getReasonString() { - return "Zone " + record.name.ace + " is in list of known SEPs, but DNSKEY from response mismatches!"; - } - } - - public static class NoTrustAnchorReason extends DnssecUnverifiedReason { - private final DnsName zone; - - public NoTrustAnchorReason(DnsName zone) { - this.zone = zone; - } - - @Override - public String getReasonString() { - return "No trust anchor was found for zone " + zone + ". Try enabling DLV"; - } - } - - public static class NoSecureEntryPointReason extends DnssecUnverifiedReason { - private final DnsName zone; - - public NoSecureEntryPointReason(DnsName zone) { - this.zone = zone; - } - - @Override - public String getReasonString() { - return "No secure entry point was found for zone " + zone; - } - } - - public static class NoRootSecureEntryPointReason extends DnssecUnverifiedReason { - public NoRootSecureEntryPointReason() { - } - - @Override - public String getReasonString() { - return "No secure entry point was found for the root zone (\"Did you forget to configure a root SEP?\")"; - } - } - - public static class NoSignaturesReason extends DnssecUnverifiedReason { - private final Question question; - - public NoSignaturesReason(Question question) { - this.question = question; - } - - @Override - public String getReasonString() { - return "No signatures were attached to answer on question for " + question.type + " at " + question.name; - } - } - - public static class NoActiveSignaturesReason extends DnssecUnverifiedReason { - private final Question question; - private final List outdatedRrSigs; - - public NoActiveSignaturesReason(Question question, List outdatedRrSigs) { - this.question = question; - assert !outdatedRrSigs.isEmpty(); - this.outdatedRrSigs = Collections.unmodifiableList(outdatedRrSigs); - } - - @Override - public String getReasonString() { - return "No currently active signatures were attached to answer on question for " + question.type + " at " + question.name; - } - - public List getOutdatedRrSigs() { - return outdatedRrSigs; - } - } - - public static class NSECDoesNotMatchReason extends DnssecUnverifiedReason { - private final Question question; - private final Record record; - - public NSECDoesNotMatchReason(Question question, Record record) { - this.question = question; - this.record = record; - } - - @Override - public String getReasonString() { - return "NSEC " + record.name + " does nat match question for " + question.type + " at " + question.name; - } - } -} diff --git a/app/src/main/java/org/minidns/dnssec/DnssecValidationFailedException.java b/app/src/main/java/org/minidns/dnssec/DnssecValidationFailedException.java deleted file mode 100644 index 1d5982f3ad..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DnssecValidationFailedException.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import org.minidns.dnsmessage.DnsMessage; -import org.minidns.dnsmessage.Question; -import org.minidns.record.Data; -import org.minidns.record.Record; - -import java.io.IOException; -import java.security.spec.InvalidKeySpecException; -import java.util.List; - -public class DnssecValidationFailedException extends IOException { - private static final long serialVersionUID = 5413184667629832742L; - - public DnssecValidationFailedException(Question question, String reason) { - super("Validation of request to " + question + " failed: " + reason); - } - - public DnssecValidationFailedException(String message) { - super(message); - } - - public DnssecValidationFailedException(String message, Throwable cause) { - super(message, cause); - } - - public DnssecValidationFailedException(Record record, String reason) { - super("Validation of record " + record + " failed: " + reason); - } - - public DnssecValidationFailedException(List> records, String reason) { - super("Validation of " + records.size() + " " + records.get(0).type + " record" + (records.size() > 1 ? "s" : "") + " failed: " + reason); - } - - public static class DataMalformedException extends DnssecValidationFailedException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private final byte[] data; - - public DataMalformedException(IOException exception, byte[] data) { - super("Malformed data", exception); - this.data = data; - } - - public DataMalformedException(String message, IOException exception, byte[] data) { - super(message, exception); - this.data = data; - } - - public byte[] getData() { - return data; - } - } - - public static class DnssecInvalidKeySpecException extends DnssecValidationFailedException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - public DnssecInvalidKeySpecException(InvalidKeySpecException exception) { - super("Invalid key spec", exception); - } - - public DnssecInvalidKeySpecException(String message, InvalidKeySpecException exception, byte[] data) { - super(message, exception); - } - - } - - public static class AuthorityDoesNotContainSoa extends DnssecValidationFailedException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private final DnsMessage response; - - public AuthorityDoesNotContainSoa(DnsMessage response) { - super("Autority does not contain SOA"); - this.response = response; - } - - public DnsMessage getResponse() { - return response; - } - } -} diff --git a/app/src/main/java/org/minidns/dnssec/DnssecValidatorInitializationException.java b/app/src/main/java/org/minidns/dnssec/DnssecValidatorInitializationException.java deleted file mode 100644 index 0340fb4698..0000000000 --- a/app/src/main/java/org/minidns/dnssec/DnssecValidatorInitializationException.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -public class DnssecValidatorInitializationException extends RuntimeException { - private static final long serialVersionUID = -1464257268053507791L; - - public DnssecValidatorInitializationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/org/minidns/dnssec/SignatureVerifier.java b/app/src/main/java/org/minidns/dnssec/SignatureVerifier.java deleted file mode 100644 index d665fc9bf9..0000000000 --- a/app/src/main/java/org/minidns/dnssec/SignatureVerifier.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import org.minidns.record.DNSKEY; -import org.minidns.record.RRSIG; - -public interface SignatureVerifier { - boolean verify(byte[] content, RRSIG rrsig, DNSKEY key) throws DnssecValidationFailedException; -} diff --git a/app/src/main/java/org/minidns/dnssec/Verifier.java b/app/src/main/java/org/minidns/dnssec/Verifier.java deleted file mode 100644 index ee33a95698..0000000000 --- a/app/src/main/java/org/minidns/dnssec/Verifier.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec; - -import org.minidns.dnslabel.DnsLabel; -import org.minidns.dnsmessage.Question; -import org.minidns.dnsname.DnsName; -import org.minidns.dnssec.DnssecUnverifiedReason.AlgorithmExceptionThrownReason; -import org.minidns.dnssec.DnssecUnverifiedReason.AlgorithmNotSupportedReason; -import org.minidns.dnssec.DnssecUnverifiedReason.NSECDoesNotMatchReason; -import org.minidns.dnssec.algorithms.AlgorithmMap; -import org.minidns.record.DNSKEY; -import org.minidns.record.Data; -import org.minidns.record.DelegatingDnssecRR; -import org.minidns.record.NSEC; -import org.minidns.record.NSEC3; -import org.minidns.record.RRSIG; -import org.minidns.record.Record; -import org.minidns.util.Base32; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -class Verifier { - private static final AlgorithmMap algorithmMap = AlgorithmMap.INSTANCE; - - public static DnssecUnverifiedReason verify(Record dnskeyRecord, DelegatingDnssecRR ds) throws DnssecValidationFailedException { - DNSKEY dnskey = dnskeyRecord.payloadData; - DigestCalculator digestCalculator = algorithmMap.getDsDigestCalculator(ds.digestType); - if (digestCalculator == null) { - return new AlgorithmNotSupportedReason(ds.digestTypeByte, ds.getType(), dnskeyRecord); - } - - byte[] dnskeyData = dnskey.toByteArray(); - byte[] dnskeyOwner = dnskeyRecord.name.getBytes(); - byte[] combined = new byte[dnskeyOwner.length + dnskeyData.length]; - System.arraycopy(dnskeyOwner, 0, combined, 0, dnskeyOwner.length); - System.arraycopy(dnskeyData, 0, combined, dnskeyOwner.length, dnskeyData.length); - byte[] digest; - try { - digest = digestCalculator.digest(combined); - } catch (Exception e) { - return new AlgorithmExceptionThrownReason(ds.digestType, "DS", dnskeyRecord, e); - } - - if (!ds.digestEquals(digest)) { - // TODO: Add 'ds' and 'digest' to this exception, and rename the exception to "DigestComparisionFailedException". - throw new DnssecValidationFailedException(dnskeyRecord, "SEP is not properly signed by parent DS!"); - } - return null; - } - - public static DnssecUnverifiedReason verify(List> records, RRSIG rrsig, DNSKEY key) throws IOException { - SignatureVerifier signatureVerifier = algorithmMap.getSignatureVerifier(rrsig.algorithm); - if (signatureVerifier == null) { - return new AlgorithmNotSupportedReason(rrsig.algorithmByte, rrsig.getType(), records.get(0)); - } - - byte[] combine = combine(rrsig, records); - if (signatureVerifier.verify(combine, rrsig, key)) { - return null; - } else { - throw new DnssecValidationFailedException(records, "Signature is invalid."); - } - } - - public static DnssecUnverifiedReason verifyNsec(Record nsecRecord, Question q) { - NSEC nsec = nsecRecord.payloadData; - if (nsecRecord.name.equals(q.name) && !nsec.types.contains(q.type)) { - // records with same name but different types exist - return null; - } else if (nsecMatches(q.name, nsecRecord.name, nsec.next)) { - return null; - } - return new NSECDoesNotMatchReason(q, nsecRecord); - } - - public static DnssecUnverifiedReason verifyNsec3(DnsName zone, Record nsec3record, Question q) { - NSEC3 nsec3 = nsec3record.payloadData; - DigestCalculator digestCalculator = algorithmMap.getNsecDigestCalculator(nsec3.hashAlgorithm); - if (digestCalculator == null) { - return new AlgorithmNotSupportedReason(nsec3.hashAlgorithmByte, nsec3.getType(), nsec3record); - } - - byte[] bytes = nsec3hash(digestCalculator, nsec3, q.name, nsec3.iterations); - String s = Base32.encodeToString(bytes); - DnsName computedNsec3Record = DnsName.from(s + "." + zone); - if (nsec3record.name.equals(computedNsec3Record)) { - if (nsec3.types.contains(q.type)) { - // TODO: Refine exception thrown in this case. - return new NSECDoesNotMatchReason(q, nsec3record); - } - return null; - } - if (nsecMatches(s, nsec3record.name.getHostpart(), Base32.encodeToString(nsec3.getNextHashed()))) { - return null; - } - return new NSECDoesNotMatchReason(q, nsec3record); - } - - static byte[] combine(RRSIG rrsig, List> records) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - - // Write RRSIG without signature - try { - rrsig.writePartialSignature(dos); - - DnsName sigName = records.get(0).name; - if (!sigName.isRootLabel()) { - if (sigName.getLabelCount() < rrsig.labels) { - // TODO: This is currently not covered by the unit tests. - throw new DnssecValidationFailedException("Invalid RRsig record"); - } - - if (sigName.getLabelCount() > rrsig.labels) { - // TODO: This is currently not covered by the unit tests. - // Expand wildcards - sigName = DnsName.from(DnsLabel.WILDCARD_LABEL, sigName.stripToLabels(rrsig.labels)); - } - } - - List recordBytes = new ArrayList<>(records.size()); - for (Record record : records) { - Record ref = new Record(sigName, record.type, record.clazzValue, rrsig.originalTtl, record.payloadData); - recordBytes.add(ref.toByteArray()); - } - - // Sort correctly (cause they might be ordered randomly) as per RFC 4034 § 6.3. - final int offset = sigName.size() + 10; // Where the RDATA begins - Collections.sort(recordBytes, new Comparator() { - @Override - public int compare(byte[] b1, byte[] b2) { - for (int i = offset; i < b1.length && i < b2.length; i++) { - if (b1[i] != b2[i]) { - return (b1[i] & 0xFF) - (b2[i] & 0xFF); - } - } - return b1.length - b2.length; - } - }); - - for (byte[] recordByte : recordBytes) { - dos.write(recordByte); - } - dos.flush(); - } catch (IOException e) { - // Never happens - throw new RuntimeException(e); - } - return bos.toByteArray(); - } - - static boolean nsecMatches(String test, String lowerBound, String upperBound) { - return nsecMatches(DnsName.from(test), DnsName.from(lowerBound), DnsName.from(upperBound)); - } - - /** - * Tests if a nsec domain name is part of an NSEC record. - * - * @param test test domain name - * @param lowerBound inclusive lower bound - * @param upperBound exclusive upper bound - * @return test domain name is covered by NSEC record - */ - static boolean nsecMatches(DnsName test, DnsName lowerBound, DnsName upperBound) { - int lowerParts = lowerBound.getLabelCount(); - int upperParts = upperBound.getLabelCount(); - int testParts = test.getLabelCount(); - - if (testParts > lowerParts && !test.isChildOf(lowerBound) && test.stripToLabels(lowerParts).compareTo(lowerBound) < 0) - return false; - if (testParts <= lowerParts && test.compareTo(lowerBound.stripToLabels(testParts)) < 0) - return false; - - if (testParts > upperParts && !test.isChildOf(upperBound) && test.stripToLabels(upperParts).compareTo(upperBound) > 0) - return false; - if (testParts <= upperParts && test.compareTo(upperBound.stripToLabels(testParts)) >= 0) - return false; - - return true; - } - - static byte[] nsec3hash(DigestCalculator digestCalculator, NSEC3 nsec3, DnsName ownerName, int iterations) { - return nsec3hash(digestCalculator, nsec3.getSalt(), ownerName.getBytes(), iterations); - } - - /** - * Derived from RFC 5155 Section 5. - * - * @param digestCalculator the digest calculator. - * @param salt the salt. - * @param data the data. - * @param iterations the number of iterations. - * @return the NSEC3 hash. - */ - static byte[] nsec3hash(DigestCalculator digestCalculator, byte[] salt, byte[] data, int iterations) { - while (iterations-- >= 0) { - byte[] combined = new byte[data.length + salt.length]; - System.arraycopy(data, 0, combined, 0, data.length); - System.arraycopy(salt, 0, combined, data.length, salt.length); - data = digestCalculator.digest(combined); - } - return data; - } -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/AlgorithmMap.java b/app/src/main/java/org/minidns/dnssec/algorithms/AlgorithmMap.java deleted file mode 100644 index 0a7af75315..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/AlgorithmMap.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.constants.DnssecConstants.DigestAlgorithm; -import org.minidns.constants.DnssecConstants.SignatureAlgorithm; -import org.minidns.dnssec.DnssecValidatorInitializationException; -import org.minidns.dnssec.DigestCalculator; -import org.minidns.dnssec.SignatureVerifier; -import org.minidns.record.NSEC3.HashAlgorithm; - -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -public final class AlgorithmMap { - private Logger LOGGER = Logger.getLogger(AlgorithmMap.class.getName()); - - public static final AlgorithmMap INSTANCE = new AlgorithmMap(); - - private final Map dsDigestMap = new HashMap<>(); - private final Map signatureMap = new HashMap<>(); - private final Map nsecDigestMap = new HashMap<>(); - - @SuppressWarnings("deprecation") - private AlgorithmMap() { - try { - dsDigestMap.put(DigestAlgorithm.SHA1, new JavaSecDigestCalculator("SHA-1")); - nsecDigestMap.put(HashAlgorithm.SHA1, new JavaSecDigestCalculator("SHA-1")); - } catch (NoSuchAlgorithmException e) { - // SHA-1 is MANDATORY - throw new DnssecValidatorInitializationException("SHA-1 is mandatory", e); - } - try { - dsDigestMap.put(DigestAlgorithm.SHA256, new JavaSecDigestCalculator("SHA-256")); - } catch (NoSuchAlgorithmException e) { - // SHA-256 is MANDATORY - throw new DnssecValidatorInitializationException("SHA-256 is mandatory", e); - } - try { - dsDigestMap.put(DigestAlgorithm.SHA384, new JavaSecDigestCalculator("SHA-384")); - } catch (NoSuchAlgorithmException e) { - // SHA-384 is OPTIONAL - LOGGER.log(Level.FINE, "Platform does not support SHA-384", e); - } - - try { - signatureMap.put(SignatureAlgorithm.RSAMD5, new RsaSignatureVerifier("MD5withRSA")); - } catch (NoSuchAlgorithmException e) { - // RSA/MD5 is DEPRECATED - LOGGER.log(Level.FINER, "Platform does not support RSA/MD5", e); - } - try { - DsaSignatureVerifier sha1withDSA = new DsaSignatureVerifier("SHA1withDSA"); - signatureMap.put(SignatureAlgorithm.DSA, sha1withDSA); - signatureMap.put(SignatureAlgorithm.DSA_NSEC3_SHA1, sha1withDSA); - } catch (NoSuchAlgorithmException e) { - // DSA/SHA-1 is OPTIONAL - LOGGER.log(Level.FINE, "Platform does not support DSA/SHA-1", e); - } - try { - RsaSignatureVerifier sha1withRSA = new RsaSignatureVerifier("SHA1withRSA"); - signatureMap.put(SignatureAlgorithm.RSASHA1, sha1withRSA); - signatureMap.put(SignatureAlgorithm.RSASHA1_NSEC3_SHA1, sha1withRSA); - } catch (NoSuchAlgorithmException e) { - throw new DnssecValidatorInitializationException("Platform does not support RSA/SHA-1", e); - } - try { - signatureMap.put(SignatureAlgorithm.RSASHA256, new RsaSignatureVerifier("SHA256withRSA")); - } catch (NoSuchAlgorithmException e) { - // RSA/SHA-256 is RECOMMENDED - LOGGER.log(Level.INFO, "Platform does not support RSA/SHA-256", e); - } - try { - signatureMap.put(SignatureAlgorithm.RSASHA512, new RsaSignatureVerifier("SHA512withRSA")); - } catch (NoSuchAlgorithmException e) { - // RSA/SHA-512 is RECOMMENDED - LOGGER.log(Level.INFO, "Platform does not support RSA/SHA-512", e); - } - try { - signatureMap.put(SignatureAlgorithm.ECC_GOST, new EcgostSignatureVerifier()); - } catch (NoSuchAlgorithmException e) { - // GOST R 34.10-2001 is OPTIONAL - LOGGER.log(Level.FINE, "Platform does not support GOST R 34.10-2001", e); - } - try { - signatureMap.put(SignatureAlgorithm.ECDSAP256SHA256, new EcdsaSignatureVerifier.P256SHA256()); - } catch (NoSuchAlgorithmException e) { - // ECDSA/SHA-256 is RECOMMENDED - LOGGER.log(Level.INFO, "Platform does not support ECDSA/SHA-256", e); - } - try { - signatureMap.put(SignatureAlgorithm.ECDSAP384SHA384, new EcdsaSignatureVerifier.P384SHA284()); - } catch (NoSuchAlgorithmException e) { - // ECDSA/SHA-384 is RECOMMENDED - LOGGER.log(Level.INFO, "Platform does not support ECDSA/SHA-384", e); - } - } - - public DigestCalculator getDsDigestCalculator(DigestAlgorithm algorithm) { - return dsDigestMap.get(algorithm); - } - - public SignatureVerifier getSignatureVerifier(SignatureAlgorithm algorithm) { - return signatureMap.get(algorithm); - } - - public DigestCalculator getNsecDigestCalculator(HashAlgorithm algorithm) { - return nsecDigestMap.get(algorithm); - } -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/DsaSignatureVerifier.java b/app/src/main/java/org/minidns/dnssec/algorithms/DsaSignatureVerifier.java deleted file mode 100644 index ee64d5f1f4..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/DsaSignatureVerifier.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException; -import org.minidns.record.DNSKEY; -import org.minidns.record.RRSIG; -import org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException; - -import java.io.ByteArrayOutputStream; -import java.io.DataInput; -import java.io.DataOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.InvalidKeySpecException; - -class DsaSignatureVerifier extends JavaSecSignatureVerifier { - private static final int LENGTH = 20; - - DsaSignatureVerifier(String algorithm) throws NoSuchAlgorithmException { - super("DSA", algorithm); - } - - @Override - protected byte[] getSignature(RRSIG rrsig) throws DataMalformedException { - DataInput dis = rrsig.getSignatureAsDataInputStream(); - - ByteArrayOutputStream bos; - try { - // Convert RFC 2536 to ASN.1 - @SuppressWarnings("unused") - byte t = dis.readByte(); - - byte[] r = new byte[LENGTH]; - dis.readFully(r); - int roff = 0; - final int rlen; - if (r[0] == 0) { - while (roff < LENGTH && r[roff] == 0) { - roff++; - } - rlen = r.length - roff; - } else if (r[0] < 0) { - rlen = r.length + 1; - } else { - rlen = r.length; - } - - byte[] s = new byte[LENGTH]; - dis.readFully(s); - int soff = 0; - final int slen; - if (s[0] == 0) { - while (soff < LENGTH && s[soff] == 0) { - soff++; - } - slen = s.length - soff; - } else if (s[0] < 0) { - slen = s.length + 1; - } else { - slen = s.length; - } - - bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - - dos.writeByte(0x30); - dos.writeByte(rlen + slen + 4); - - dos.writeByte(0x2); - dos.writeByte(rlen); - if (rlen > LENGTH) - dos.writeByte(0); - dos.write(r, roff, LENGTH - roff); - - dos.writeByte(0x2); - dos.writeByte(slen); - if (slen > LENGTH) - dos.writeByte(0); - dos.write(s, soff, LENGTH - soff); - } catch (IOException e) { - throw new DataMalformedException(e, rrsig.getSignature()); - } - - return bos.toByteArray(); - } - - @Override - protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException { - DataInput dis = key.getKeyAsDataInputStream(); - BigInteger subPrime, prime, base, pubKey; - - try { - int t = dis.readUnsignedByte(); - - byte[] subPrimeBytes = new byte[LENGTH]; - dis.readFully(subPrimeBytes); - subPrime = new BigInteger(1, subPrimeBytes); - - byte[] primeBytes = new byte[64 + t * 8]; - dis.readFully(primeBytes); - prime = new BigInteger(1, primeBytes); - - byte[] baseBytes = new byte[64 + t * 8]; - dis.readFully(baseBytes); - base = new BigInteger(1, baseBytes); - - byte[] pubKeyBytes = new byte[64 + t * 8]; - dis.readFully(pubKeyBytes); - pubKey = new BigInteger(1, pubKeyBytes); - } catch (IOException e) { - throw new DataMalformedException(e, key.getKey()); - } - - try { - return getKeyFactory().generatePublic(new DSAPublicKeySpec(pubKey, prime, subPrime, base)); - } catch (InvalidKeySpecException e) { - throw new DnssecInvalidKeySpecException(e); - } - } -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/EcdsaSignatureVerifier.java b/app/src/main/java/org/minidns/dnssec/algorithms/EcdsaSignatureVerifier.java deleted file mode 100644 index e7af9498ab..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/EcdsaSignatureVerifier.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException; -import org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException; -import org.minidns.record.DNSKEY; -import org.minidns.record.RRSIG; - -import java.io.ByteArrayOutputStream; -import java.io.DataInput; -import java.io.DataOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.ECFieldFp; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.EllipticCurve; -import java.security.spec.InvalidKeySpecException; - -abstract class EcdsaSignatureVerifier extends JavaSecSignatureVerifier { - private final ECParameterSpec spec; - private final int length; - - EcdsaSignatureVerifier(BigInteger[] spec, int length, String algorithm) throws NoSuchAlgorithmException { - this(new ECParameterSpec(new EllipticCurve(new ECFieldFp(spec[0]), spec[1], spec[2]), new ECPoint(spec[3], spec[4]), spec[5], 1), length, algorithm); - } - - EcdsaSignatureVerifier(ECParameterSpec spec, int length, String algorithm) throws NoSuchAlgorithmException { - super("EC", algorithm); - this.length = length; - this.spec = spec; - } - - @Override - protected byte[] getSignature(RRSIG rrsig) throws DataMalformedException { - DataInput dis = rrsig.getSignatureAsDataInputStream(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - - try { - byte[] r = new byte[length]; - dis.readFully(r); - int rlen = (r[0] < 0) ? length + 1 : length; - - byte[] s = new byte[length]; - dis.readFully(s); - int slen = (s[0] < 0) ? length + 1 : length; - - dos.writeByte(0x30); - dos.writeByte(rlen + slen + 4); - - dos.writeByte(0x2); - dos.writeByte(rlen); - if (rlen > length) dos.writeByte(0); - dos.write(r); - - dos.writeByte(0x2); - dos.writeByte(slen); - if (slen > length) dos.writeByte(0); - dos.write(s); - } catch (IOException e) { - throw new DataMalformedException(e, rrsig.getSignature()); - } - - return bos.toByteArray(); - } - - @Override - protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException { - DataInput dis = key.getKeyAsDataInputStream(); - BigInteger x, y; - - try { - byte[] xBytes = new byte[length]; - dis.readFully(xBytes); - x = new BigInteger(1, xBytes); - - byte[] yBytes = new byte[length]; - dis.readFully(yBytes); - y = new BigInteger(1, yBytes); - } catch (IOException e) { - throw new DataMalformedException(e, key.getKey()); - } - - try { - return getKeyFactory().generatePublic(new ECPublicKeySpec(new ECPoint(x, y), spec)); - } catch (InvalidKeySpecException e) { - throw new DnssecInvalidKeySpecException(e); - } - } - - public static class P256SHA256 extends EcdsaSignatureVerifier { - private static BigInteger[] SPEC = { - new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16), - new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16), - new BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16), - new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16), - new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16), - new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16) - }; - - P256SHA256() throws NoSuchAlgorithmException { - super(SPEC, 32, "SHA256withECDSA"); - } - } - - public static class P384SHA284 extends EcdsaSignatureVerifier { - private static BigInteger[] SPEC = { - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16), - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16), - new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16), - new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16), - new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16), - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16) - }; - - P384SHA284() throws NoSuchAlgorithmException { - super(SPEC, 48, "SHA384withECDSA"); - } - } -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/EcgostSignatureVerifier.java b/app/src/main/java/org/minidns/dnssec/algorithms/EcgostSignatureVerifier.java deleted file mode 100644 index 2a70a41ae3..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/EcgostSignatureVerifier.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException; -import org.minidns.record.DNSKEY; -import org.minidns.record.RRSIG; -import org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException; - -import java.io.DataInput; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.ECFieldFp; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.EllipticCurve; -import java.security.spec.InvalidKeySpecException; - -class EcgostSignatureVerifier extends JavaSecSignatureVerifier { - private static final int LENGTH = 32; - private static final ECParameterSpec SPEC = new ECParameterSpec( - new EllipticCurve( - new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16)), - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16), - new BigInteger("A6", 16) - ), - new ECPoint(BigInteger.ONE, new BigInteger("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", 16)), - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", 16), - 1 - ); - - EcgostSignatureVerifier() throws NoSuchAlgorithmException { - super("ECGOST3410", "GOST3411withECGOST3410"); - } - - @Override - protected byte[] getSignature(RRSIG rrsig) { - return rrsig.getSignature(); - } - - @Override - protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException { - DataInput dis = key.getKeyAsDataInputStream(); - BigInteger x, y; - - try { - byte[] xBytes = new byte[LENGTH]; - dis.readFully(xBytes); - reverse(xBytes); - x = new BigInteger(1, xBytes); - - byte[] yBytes = new byte[LENGTH]; - dis.readFully(yBytes); - reverse(yBytes); - y = new BigInteger(1, yBytes); - } catch (IOException e) { - throw new DataMalformedException(e, key.getKey()); - } - - try { - return getKeyFactory().generatePublic(new ECPublicKeySpec(new ECPoint(x, y), SPEC)); - } catch (InvalidKeySpecException e) { - throw new DnssecInvalidKeySpecException(e); - } - } - - private static void reverse(byte[] array) { - for (int i = 0; i < array.length / 2; i++) { - int j = array.length - i - 1; - byte tmp = array[i]; - array[i] = array[j]; - array[j] = tmp; - } - } -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/JavaSecDigestCalculator.java b/app/src/main/java/org/minidns/dnssec/algorithms/JavaSecDigestCalculator.java deleted file mode 100644 index 3be9f616cd..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/JavaSecDigestCalculator.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.dnssec.DigestCalculator; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class JavaSecDigestCalculator implements DigestCalculator { - private MessageDigest md; - - public JavaSecDigestCalculator(String algorithm) throws NoSuchAlgorithmException { - md = MessageDigest.getInstance(algorithm); - } - - @Override - public byte[] digest(byte[] bytes) { - return md.digest(bytes); - } -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/JavaSecSignatureVerifier.java b/app/src/main/java/org/minidns/dnssec/algorithms/JavaSecSignatureVerifier.java deleted file mode 100644 index 8069c17a44..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/JavaSecSignatureVerifier.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.dnssec.DnssecValidationFailedException; -import org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException; -import org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException; -import org.minidns.dnssec.SignatureVerifier; -import org.minidns.record.DNSKEY; -import org.minidns.record.RRSIG; - -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; - -public abstract class JavaSecSignatureVerifier implements SignatureVerifier { - private final KeyFactory keyFactory; - private final String signatureAlgorithm; - - public JavaSecSignatureVerifier(String keyAlgorithm, String signatureAlgorithm) throws NoSuchAlgorithmException { - keyFactory = KeyFactory.getInstance(keyAlgorithm); - this.signatureAlgorithm = signatureAlgorithm; - - // Verify signature algorithm to be valid - Signature.getInstance(signatureAlgorithm); - } - - public KeyFactory getKeyFactory() { - return keyFactory; - } - - @Override - public boolean verify(byte[] content, RRSIG rrsig, DNSKEY key) throws DnssecValidationFailedException { - try { - PublicKey publicKey = getPublicKey(key); - Signature signature = Signature.getInstance(signatureAlgorithm); - signature.initVerify(publicKey); - signature.update(content); - return signature.verify(getSignature(rrsig)); - } catch (NoSuchAlgorithmException e) { - // We checked against this before, it should never happen! - throw new AssertionError(e); - } catch (InvalidKeyException | SignatureException | ArithmeticException e) { - throw new DnssecValidationFailedException("Validating signature failed", e); - } - } - - protected abstract byte[] getSignature(RRSIG rrsig) throws DataMalformedException; - - protected abstract PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException; -} diff --git a/app/src/main/java/org/minidns/dnssec/algorithms/RsaSignatureVerifier.java b/app/src/main/java/org/minidns/dnssec/algorithms/RsaSignatureVerifier.java deleted file mode 100644 index 9fb7712c87..0000000000 --- a/app/src/main/java/org/minidns/dnssec/algorithms/RsaSignatureVerifier.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors - * - * This software is licensed under the Apache License, Version 2.0, - * the GNU Lesser General Public License version 2 or later ("LGPL") - * and the WTFPL. - * You may choose either license to govern your use of this software only - * upon the condition that you accept all of the terms of either - * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. - */ -package org.minidns.dnssec.algorithms; - -import org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException; -import org.minidns.record.DNSKEY; -import org.minidns.record.RRSIG; -import org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException; - -import java.io.DataInput; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPublicKeySpec; - -class RsaSignatureVerifier extends JavaSecSignatureVerifier { - RsaSignatureVerifier(String algorithm) throws NoSuchAlgorithmException { - super("RSA", algorithm); - } - - @Override - protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException { - DataInput dis = key.getKeyAsDataInputStream(); - BigInteger exponent, modulus; - - try { - int exponentLength = dis.readUnsignedByte(); - int bytesRead = 1; - if (exponentLength == 0) { - bytesRead += 2; - exponentLength = dis.readUnsignedShort(); - } - - byte[] exponentBytes = new byte[exponentLength]; - dis.readFully(exponentBytes); - bytesRead += exponentLength; - exponent = new BigInteger(1, exponentBytes); - - byte[] modulusBytes = new byte[key.getKeyLength() - bytesRead]; - dis.readFully(modulusBytes); - modulus = new BigInteger(1, modulusBytes); - } catch (IOException e) { - throw new DataMalformedException(e, key.getKey()); - } - - try { - return getKeyFactory().generatePublic(new RSAPublicKeySpec(modulus, exponent)); - } catch (InvalidKeySpecException e) { - throw new DnssecInvalidKeySpecException(e); - } - } - - @Override - protected byte[] getSignature(RRSIG rrsig) { - return rrsig.getSignature(); - } -}