diff --git a/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneCertificateException.java b/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneCertificateException.java new file mode 100644 index 00000000..70b5c8fe --- /dev/null +++ b/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneCertificateException.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 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 de.measite.minidns.dane; + +import java.security.cert.CertificateException; +import java.util.Collections; +import java.util.List; + +import de.measite.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/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneVerifier.java b/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneVerifier.java index 71d32648..15bea4e0 100644 --- a/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneVerifier.java +++ b/minidns-dnssec/src/main/java/de/measite/minidns/dane/DaneVerifier.java @@ -40,6 +40,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -120,15 +121,28 @@ public boolean verifyCertificateChain(X509Certificate[] chain, String hostName, LOGGER.info(msg); return false; } + + List certificateMismatchExceptions = new LinkedList<>(); boolean verified = false; for (Record record : res.getAnswers()) { if (record.type == Record.TYPE.TLSA && record.name.equals(req)) { TLSA tlsa = (TLSA) record.payloadData; - verified |= checkCertificateMatches(chain[0], tlsa, hostName); + 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; } } + if (!verified && !certificateMismatchExceptions.isEmpty()) { + throw new DaneCertificateException.MultipleCertificateMismatchExceptions(certificateMismatchExceptions); + } + return verified; } @@ -182,7 +196,7 @@ private static boolean checkCertificateMatches(X509Certificate cert, TLSA tlsa, boolean matches = Arrays.equals(comp, tlsa.certificateAssociation); if (!matches) { - throw new CertificateException("Verification using TLSA failed"); + throw new DaneCertificateException.CertificateMismatch(tlsa, comp); } // domain issued certificate does not require further verification,