From 379f184a41db31971422dc91b073eef63289a933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Wed, 29 Jun 2022 19:52:57 +0200 Subject: [PATCH] Add generator for DNS challenge resource name --- .../acme4j/challenge/Dns01Challenge.java | 34 +++++++++++++++++++ .../acme4j/challenge/DnsChallengeTest.java | 15 ++++++++ .../shredzone/acme4j/example/ClientTest.java | 9 +++-- .../shredzone/acme4j/it/BammBammClient.java | 4 +-- .../shredzone/acme4j/it/pebble/OrderIT.java | 2 +- .../acme4j/it/pebble/OrderWildcardIT.java | 2 +- src/doc/docs/challenge/dns-01.md | 3 +- src/doc/docs/example.md | 9 +++-- 8 files changed, 63 insertions(+), 15 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java index 098cf3b8..c188d309 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java @@ -16,6 +16,7 @@ package org.shredzone.acme4j.challenge; import static org.shredzone.acme4j.toolbox.AcmeUtils.base64UrlEncode; import static org.shredzone.acme4j.toolbox.AcmeUtils.sha256hash; +import org.shredzone.acme4j.Identifier; import org.shredzone.acme4j.Login; import org.shredzone.acme4j.toolbox.JSON; @@ -30,6 +31,39 @@ public class Dns01Challenge extends TokenChallenge { */ public static final String TYPE = "dns-01"; + /** + * The prefix of the domain name to be used for the DNS TXT record. + */ + public static final String RECORD_NAME_PREFIX = "_acme-challenge"; + + /** + * Converts a domain identifier to the Resource Record name to be used for the DNS TXT + * record. + * + * @param identifier + * Domain {@link Identifier} of the domain to be validated + * @return Resource Record name (e.g. {@code _acme-challenge.www.example.org.}, note + * the trailing full stop character). + * @since 2.14 + */ + public static String toRRName(Identifier identifier) { + return toRRName(identifier.getDomain()); + } + + /** + * Converts a domain identifier to the Resource Record name to be used for the DNS TXT + * record. + * + * @param domain + * Domain name to be validated + * @return Resource Record name (e.g. {@code _acme-challenge.www.example.org.}, note + * the trailing full stop character). + * @since 2.14 + */ + public static String toRRName(String domain) { + return RECORD_NAME_PREFIX + '.' + domain + '.'; + } + /** * Creates a new generic {@link Dns01Challenge} object. * diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java index 48ede64e..2ad63811 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java @@ -15,11 +15,14 @@ package org.shredzone.acme4j.challenge; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; import org.junit.jupiter.api.Test; +import org.shredzone.acme4j.Identifier; import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.TestUtils; @@ -48,4 +51,16 @@ public class DnsChallengeTest { assertThatJson(response.toString()).isEqualTo("{}"); } + @Test + public void testToRRName() { + assertThat(Dns01Challenge.toRRName("www.example.org")) + .isEqualTo("_acme-challenge.www.example.org."); + assertThat(Dns01Challenge.toRRName(Identifier.dns("www.example.org"))) + .isEqualTo("_acme-challenge.www.example.org."); + assertThatExceptionOfType(AcmeProtocolException.class) + .isThrownBy(() -> Dns01Challenge.toRRName(Identifier.ip("127.0.0.10"))); + assertThat(Dns01Challenge.RECORD_NAME_PREFIX) + .isEqualTo("_acme-challenge"); + } + } diff --git a/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java b/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java index 178e5707..40b4bcbc 100644 --- a/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java +++ b/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java @@ -362,15 +362,14 @@ public class ClientTest { // Output the challenge, wait for acknowledge... LOG.info("Please create a TXT record:"); - LOG.info("_acme-challenge.{}. IN TXT {}", - auth.getIdentifier().getDomain(), challenge.getDigest()); + LOG.info("{} IN TXT {}", + Dns01Challenge.toRRName(auth.getIdentifier()), challenge.getDigest()); LOG.info("If you're ready, dismiss the dialog..."); StringBuilder message = new StringBuilder(); message.append("Please create a TXT record:\n\n"); - message.append("_acme-challenge.") - .append(auth.getIdentifier().getDomain()) - .append(". IN TXT ") + message.append(Dns01Challenge.toRRName(auth.getIdentifier())) + .append(" IN TXT ") .append(challenge.getDigest()); acceptChallenge(message.toString()); diff --git a/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java b/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java index 7ab6f801..d9fc1051 100644 --- a/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java +++ b/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java @@ -107,13 +107,13 @@ public class BammBammClient { * another TXT Record is set, it will replace the existing one. * * @param domain - * Domain to add the TXT Record to + * Domain name to add the TXT Record to * @param txt * TXT record to add */ public void dnsAddTxtRecord(String domain, String txt) throws IOException { JSONBuilder jb = new JSONBuilder(); - jb.put("host", domain + '.'); + jb.put("host", domain); jb.put("value", txt); sendRequest("set-txt", jb.toString()); } diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java index 0777cde2..3ab19af6 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java @@ -81,7 +81,7 @@ public class OrderIT extends PebbleITBase { Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE); assertThat(challenge).isNotNull(); - String challengeDomainName = "_acme-challenge." + auth.getIdentifier().getDomain(); + String challengeDomainName = Dns01Challenge.toRRName(auth.getIdentifier()); client.dnsAddTxtRecord(challengeDomainName, challenge.getDigest()); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java index bf39ccf1..e7a7bdba 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java @@ -87,7 +87,7 @@ public class OrderWildcardIT extends PebbleITBase { Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE); assertThat(challenge).isNotNull(); - String challengeDomainName = "_acme-challenge." + TEST_DOMAIN; + String challengeDomainName = Dns01Challenge.toRRName(TEST_DOMAIN); client.dnsAddTxtRecord(challengeDomainName, challenge.getDigest()); cleanup(() -> client.dnsRemoveTxtRecord(challengeDomainName)); diff --git a/src/doc/docs/challenge/dns-01.md b/src/doc/docs/challenge/dns-01.md index 2c571cfa..95d4e3fa 100644 --- a/src/doc/docs/challenge/dns-01.md +++ b/src/doc/docs/challenge/dns-01.md @@ -8,9 +8,10 @@ With the `dns-01` challenge, you prove to the CA that you are able to control th Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.class); String domain = auth.getIdentifier().getDomain(); +String resourceName = Dns01Challenge.toRRName(auth.getIdentifier()); String digest = challenge.getDigest(); ``` -The CA expects a TXT record at `_acme-challenge.${domain}` with the `digest` string as value. +The CA expects a TXT record at `_acme-challenge.${domain}.` with the `digest` string as value. The `Dns01Challenge.toRRName()` method converts the domain name to a resource record name (including the trailing full stop, e.g. `_acme-challenge.www.example.org.`). The `_acme-challenge` prefix is also available as constant (`Dns01Challenge.RECORD_NAME_PREFIX`). The validation was successful if the CA was able to fetch the TXT record and got the correct `digest` returned. diff --git a/src/doc/docs/example.md b/src/doc/docs/example.md index bf6f7ec6..4d4abd52 100644 --- a/src/doc/docs/example.md +++ b/src/doc/docs/example.md @@ -348,15 +348,14 @@ public Challenge dnsChallenge(Authorization auth) throws AcmeException { // Output the challenge, wait for acknowledge... LOG.info("Please create a TXT record:"); - LOG.info("_acme-challenge.{}. IN TXT {}", - auth.getIdentifier().getDomain(), challenge.getDigest()); + LOG.info("{} IN TXT {}", + Dns01Challenge.toRRName(auth.getIdentifier()), challenge.getDigest()); LOG.info("If you're ready, dismiss the dialog..."); StringBuilder message = new StringBuilder(); message.append("Please create a TXT record:\n\n"); - message.append("_acme-challenge.") - .append(auth.getIdentifier().getDomain()) - .append(". IN TXT ") + message.append(Dns01Challenge.toRRName(auth.getIdentifier())) + .append(" IN TXT ") .append(challenge.getDigest()); acceptChallenge(message.toString());