Allow setting DS records in create_domain and update_domain

The DS records consist of 4 values:
- keyTag: unsigned short (2 bytes)
- alg: unsigned byte
- digestType: unsigned byte
- digest: binary hex

NOTE: the current CL doesn't support keyData, neither as the optional field in dsData nor as a replacement for dsData

The command tool accepts DS records as a string, where the 4 values are given
as one string separated by white-spaces as follows:
<keyTag> <alg>  <digestType>  <digest>

e.g. something like:
60485 5  2  D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A

which is how it's written in Zone files, allowing easy copy-paste from existing values.
ommas is confusing when using spaces.

The various "numbers" (keyTag, alg, digestType) are only checked that they are
positive integers - the rest is left for the server.

digest it checked to be an even-lengthed hex string.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=184583068
This commit is contained in:
guyben 2018-02-05 14:05:51 -08:00 committed by jianglai
parent e5b000638b
commit 2e62ad2658
17 changed files with 565 additions and 5 deletions

View file

@ -75,6 +75,7 @@ java_library(
"@com_google_appengine_api_1_0_sdk",
"@com_google_appengine_remote_api",
"@com_google_appengine_remote_api//:link",
"@com_google_auto_value",
"@com_google_code_findbugs_jsr305",
"@com_google_dagger",
"@com_google_guava",

View file

@ -59,7 +59,8 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
"registrant", registrant,
"admins", admins,
"techs", techs,
"password", password));
"password", password,
"dsRecords", DsRecord.convertToSoy(dsRecords)));
}
}
}

View file

@ -15,11 +15,20 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.CollectionUtils.findDuplicates;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import com.google.auto.value.AutoValue;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.BaseEncoding;
import com.google.template.soy.data.SoyListData;
import com.google.template.soy.data.SoyMapData;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -67,8 +76,78 @@ abstract class CreateOrUpdateDomainCommand extends MutatingEppToolCommand {
)
String password;
@Parameter(
names = "--ds_records",
description =
"Comma-separated list of DS records. Each DS record is given as "
+ "<keyTag> <alg> <digestType> <digest>, in order, as it appears in the Zonefile.",
converter = DsRecordConverter.class
)
List<DsRecord> dsRecords = new ArrayList<>();
Set<String> domains;
@AutoValue
abstract static class DsRecord {
private static final Splitter SPLITTER =
Splitter.on(CharMatcher.whitespace()).omitEmptyStrings();
public abstract int keyTag();
public abstract int alg();
public abstract int digestType();
public abstract String digest();
private static DsRecord create(int keyTag, int alg, int digestType, String digest) {
digest = Ascii.toUpperCase(digest);
checkArgument(
BaseEncoding.base16().canDecode(digest),
"digest should be even-lengthed hex, but is %s (length %s)",
digest,
digest.length());
return new AutoValue_CreateOrUpdateDomainCommand_DsRecord(keyTag, alg, digestType, digest);
}
/**
* Parses a string representation of the DS record.
*
* <p>The string format accepted is "[keyTag] [alg] [digestType] [digest]" (i.e., the 4
* arguments separated by any number of spaces, as it appears in the Zone file)
*/
public static DsRecord parse(String dsRecord) {
List<String> elements = SPLITTER.splitToList(dsRecord);
checkArgument(
elements.size() == 4,
"dsRecord %s should have 4 parts, but has %s",
dsRecord,
elements.size());
return DsRecord.create(
Integer.parseUnsignedInt(elements.get(0)),
Integer.parseUnsignedInt(elements.get(1)),
Integer.parseUnsignedInt(elements.get(2)),
elements.get(3));
}
public SoyMapData toSoyData() {
return new SoyMapData(
"keyTag", keyTag(),
"alg", alg(),
"digestType", digestType(),
"digest", digest());
}
public static SoyListData convertToSoy(List<DsRecord> dsRecords) {
return new SoyListData(
dsRecords.stream().map(DsRecord::toSoyData).collect(toImmutableList()));
}
}
public static class DsRecordConverter implements IStringConverter<DsRecord> {
@Override
public DsRecord convert(String dsRecord) {
return DsRecord.parse(dsRecord);
}
}
@Override
protected void initEppToolCommand() throws Exception {
checkArgument(nameservers.size() <= 13, "There can be at most 13 nameservers.");

View file

@ -71,7 +71,11 @@ public class LockDomainCommand extends LockOrUnlockDomainCommand {
"removeAdmins", ImmutableList.of(),
"removeTechs", ImmutableList.of(),
"removeStatuses", ImmutableList.of(),
"change", false));
"change", false,
"secdns", false,
"addDsRecords", ImmutableList.of(),
"removeDsRecords", ImmutableList.of(),
"removeAllDsRecords", false));
}
}
}

View file

@ -72,7 +72,11 @@ public class UnlockDomainCommand extends LockOrUnlockDomainCommand {
"removeTechs", ImmutableList.of(),
"removeStatuses",
statusesToRemove.stream().map(StatusValue::getXmlName).collect(toImmutableList()),
"change", false));
"change", false,
"secdns", false,
"addDsRecords", ImmutableList.of(),
"removeDsRecords", ImmutableList.of(),
"removeAllDsRecords", false));
}
}
}

View file

@ -70,6 +70,13 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
)
private List<String> addStatuses = new ArrayList<>();
@Parameter(
names = "--add_ds_records",
description = "DS records to add. Cannot be set if --ds_records or --clear_ds_records is set.",
converter = DsRecordConverter.class
)
private List<DsRecord> addDsRecords = new ArrayList<>();
@Parameter(
names = "--remove_nameservers",
description =
@ -96,6 +103,21 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
)
private List<String> removeStatuses = new ArrayList<>();
@Parameter(
names = "--remove_ds_records",
description =
"DS records to remove. Cannot be set if --ds_records or --clear_ds_records is set.",
converter = DsRecordConverter.class
)
private List<DsRecord> removeDsRecords = new ArrayList<>();
@Parameter(
names = "--clear_ds_records",
description =
"removes all DS records. Is implied true if --ds_records is set."
)
boolean clearDsRecords = false;
@Override
protected void initMutatingEppToolCommand() {
if (!nameservers.isEmpty()) {
@ -123,6 +145,15 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
+ "you cannot use the add_statuses and remove_statuses flags.");
}
if (!dsRecords.isEmpty() || clearDsRecords){
checkArgument(
addDsRecords.isEmpty() && removeDsRecords.isEmpty(),
"If you provide the ds_records or clear_ds_records flags, "
+ "you cannot use the add_ds_records and remove_ds_records flags.");
addDsRecords = dsRecords;
clearDsRecords = true;
}
for (String domain : domains) {
if (!nameservers.isEmpty() || !admins.isEmpty() || !techs.isEmpty() || !statuses.isEmpty()) {
DateTime now = DateTime.now(UTC);
@ -185,7 +216,13 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
boolean change = registrant != null || password != null;
if (!add && !remove && !change) {
boolean secdns =
!addDsRecords.isEmpty()
|| !removeDsRecords.isEmpty()
|| !dsRecords.isEmpty()
|| clearDsRecords;
if (!add && !remove && !change && !secdns) {
logger.infofmt("No changes need to be made to domain %s", domain);
continue;
}
@ -207,7 +244,11 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
"removeStatuses", removeStatuses,
"change", change,
"registrant", registrant,
"password", password));
"password", password,
"secdns", secdns,
"addDsRecords", DsRecord.convertToSoy(addDsRecords),
"removeDsRecords", DsRecord.convertToSoy(removeDsRecords),
"removeAllDsRecords", clearDsRecords));
}
}

View file

@ -24,6 +24,7 @@
{@param admins: list<string>}
{@param techs: list<string>}
{@param password: string}
{@param dsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
@ -53,6 +54,20 @@
</domain:authInfo>
</domain:create>
</create>
{if length($dsRecords) > 0}
<extension>
<secDNS:create xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
{for $dsRecord in $dsRecords}
<secDNS:dsData>
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:create>
</extension>
{/if}
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -31,6 +31,10 @@
{@param change: bool}
{@param? registrant: string}
{@param? password: string}
{@param secdns: bool}
{@param addDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
{@param removeDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
{@param removeAllDsRecords: bool}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
@ -92,6 +96,41 @@
{/if}
</domain:update>
</update>
{if $secdns}
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
{if $removeAllDsRecords}
<secDNS:rem>
<secDNS:all>true</secDNS:all>
</secDNS:rem>
{/if}
{if length($removeDsRecords) > 0}
<secDNS:rem>
{for $dsRecord in $removeDsRecords}
<secDNS:dsData>
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:rem>
{/if}
{if length($addDsRecords) > 0}
<secDNS:add>
{for $dsRecord in $addDsRecords}
<secDNS:dsData>
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:add>
{/if}
</secDNS:update>
</extension>
{/if}
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -40,6 +40,8 @@ public class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomain
"--admins=crr-admin",
"--techs=crr-tech",
"--password=2fooBAR",
"--ds_records=1 2 3 abcd,4 5 6 EF01",
"--ds_records=60485 5 2 D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A",
"example.tld");
eppVerifier.verifySent("domain_create_complete.xml");
}
@ -190,4 +192,100 @@ public class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomain
"--domain=example.tld"));
assertThat(thrown).hasMessageThat().contains("--period");
}
@Test
public void testFailure_dsRecordsNot4Parts() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--registrant=crr-admin",
"--admins=crr-admin",
"--techs=crr-tech",
"--ds_records=1 2 3 ab cd",
"example.tld"));
assertThat(thrown).hasMessageThat().contains("should have 4 parts, but has 5");
}
@Test
public void testFailure_keyTagNotNumber() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--registrant=crr-admin",
"--admins=crr-admin",
"--techs=crr-tech",
"--ds_records=x 2 3 abcd",
"example.tld"));
assertThat(thrown).hasMessageThat().contains("\"x\"");
}
@Test
public void testFailure_algNotNumber() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--registrant=crr-admin",
"--admins=crr-admin",
"--techs=crr-tech",
"--ds_records=1 x 3 abcd",
"example.tld"));
assertThat(thrown).hasMessageThat().contains("\"x\"");
}
@Test
public void testFailure_digestTypeNotNumber() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--registrant=crr-admin",
"--admins=crr-admin",
"--techs=crr-tech",
"--ds_records=1 2 x abcd",
"example.tld"));
assertThat(thrown).hasMessageThat().contains("\"x\"");
}
@Test
public void testFailure_digestNotHex() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--registrant=crr-admin",
"--admins=crr-admin",
"--techs=crr-tech",
"--ds_records=1 2 3 xbcd",
"example.tld"));
assertThat(thrown).hasMessageThat().contains("XBCD");
}
@Test
public void testFailure_digestNotEvenLengthed() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--registrant=crr-admin",
"--admins=crr-admin",
"--techs=crr-tech",
"--ds_records=1 2 3 abcde",
"example.tld"));
assertThat(thrown).hasMessageThat().contains("length 5");
}
}

View file

@ -42,10 +42,12 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
"--add_admins=crr-admin2",
"--add_techs=crr-tech2",
"--add_statuses=serverDeleteProhibited",
"--add_ds_records=1 2 3 abcd,4 5 6 EF01",
"--remove_nameservers=ns4.zdns.google",
"--remove_admins=crr-admin1",
"--remove_techs=crr-tech1",
"--remove_statuses=serverHold",
"--remove_ds_records=7 8 9 12ab,6 5 4 34CD",
"--registrant=crr-admin",
"--password=2fooBAR",
"example.tld");
@ -60,10 +62,12 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
"--add_admins=crr-admin2",
"--add_techs=crr-tech2",
"--add_statuses=serverDeleteProhibited",
"--add_ds_records=1 2 3 abcd,4 5 6 EF01",
"--remove_nameservers=ns4.zdns.google",
"--remove_admins=crr-admin1",
"--remove_techs=crr-tech1",
"--remove_statuses=serverHold",
"--remove_ds_records=7 8 9 12ab,6 5 4 34CD",
"--registrant=crr-admin",
"--password=2fooBAR",
"example.tld",
@ -81,6 +85,7 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
"--add_admins=crr-admin2",
"--add_techs=crr-tech2",
"--add_statuses=serverDeleteProhibited",
"--add_ds_records=1 2 3 abcd,4 5 6 EF01",
"example.tld");
eppVerifier.verifySent("domain_update_add.xml");
}
@ -93,6 +98,7 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
"--remove_admins=crr-admin1",
"--remove_techs=crr-tech1",
"--remove_statuses=serverHold",
"--remove_ds_records=7 8 9 12ab,6 5 4 34CD",
"example.tld");
eppVerifier.verifySent("domain_update_remove.xml");
}
@ -165,6 +171,32 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
eppVerifier.verifySent("domain_update_set_statuses.xml");
}
@Test
public void testSuccess_setDsRecords() throws Exception {
runCommandForced(
"--client=NewRegistrar", "--ds_records=1 2 3 abcd,4 5 6 EF01", "example.tld");
eppVerifier.verifySent("domain_update_set_ds_records.xml");
}
@Test
public void testSuccess_setDsRecords_withUnneededClear() throws Exception {
runCommandForced(
"--client=NewRegistrar",
"--ds_records=1 2 3 abcd,4 5 6 EF01",
"--clear_ds_records",
"example.tld");
eppVerifier.verifySent("domain_update_set_ds_records.xml");
}
@Test
public void testSuccess_clearDsRecords() throws Exception {
runCommandForced(
"--client=NewRegistrar",
"--clear_ds_records",
"example.tld");
eppVerifier.verifySent("domain_update_clear_ds_records.xml");
}
@Test
public void testFailure_cantUpdateRegistryLockedDomainEvenAsSuperuser() throws Exception {
HostResource host = persistActiveHost("ns1.zdns.google");
@ -385,4 +417,76 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
"If you provide the statuses flag, "
+ "you cannot use the add_statuses and remove_statuses flags.");
}
@Test
public void testFailure_provideDsRecordsAndAddDsRecords() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--add_ds_records=1 2 3 abcd",
"--ds_records=4 5 6 EF01",
"example.tld"));
assertThat(thrown)
.hasMessageThat()
.contains(
"If you provide the ds_records or clear_ds_records flags, "
+ "you cannot use the add_ds_records and remove_ds_records flags.");
}
@Test
public void testFailure_provideDsRecordsAndRemoveDsRecords() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--remove_ds_records=7 8 9 12ab",
"--ds_records=4 5 6 EF01",
"example.tld"));
assertThat(thrown)
.hasMessageThat()
.contains(
"If you provide the ds_records or clear_ds_records flags, "
+ "you cannot use the add_ds_records and remove_ds_records flags.");
}
@Test
public void testFailure_clearDsRecordsAndAddDsRecords() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--add_ds_records=1 2 3 abcd",
"--clear_ds_records",
"example.tld"));
assertThat(thrown)
.hasMessageThat()
.contains(
"If you provide the ds_records or clear_ds_records flags, "
+ "you cannot use the add_ds_records and remove_ds_records flags.");
}
@Test
public void testFailure_clearDsRecordsAndRemoveDsRecords() throws Exception {
IllegalArgumentException thrown =
expectThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--client=NewRegistrar",
"--remove_ds_records=7 8 9 12ab",
"--clear_ds_records",
"example.tld"));
assertThat(thrown)
.hasMessageThat()
.contains(
"If you provide the ds_records or clear_ds_records flags, "
+ "you cannot use the add_ds_records and remove_ds_records flags.");
}
}

View file

@ -20,6 +20,28 @@
</domain:authInfo>
</domain:create>
</create>
<extension>
<secDNS:create xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:dsData>
<secDNS:keyTag>1</secDNS:keyTag>
<secDNS:alg>2</secDNS:alg>
<secDNS:digestType>3</secDNS:digestType>
<secDNS:digest>ABCD</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>4</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>6</secDNS:digestType>
<secDNS:digest>EF01</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>60485</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>2</secDNS:digestType>
<secDNS:digest>D4B7D520E7BB5F0F67674A0CCEB1E3E0614B93C4F9E99B8383F6A1E4469DA50A</secDNS:digest>
</secDNS:dsData>
</secDNS:create>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -16,6 +16,24 @@
</domain:add>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:add>
<secDNS:dsData>
<secDNS:keyTag>1</secDNS:keyTag>
<secDNS:alg>2</secDNS:alg>
<secDNS:digestType>3</secDNS:digestType>
<secDNS:digest>ABCD</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>4</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>6</secDNS:digestType>
<secDNS:digest>EF01</secDNS:digest>
</secDNS:dsData>
</secDNS:add>
</secDNS:update>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:rem>
<secDNS:all>true</secDNS:all>
</secDNS:rem>
</secDNS:update>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -30,6 +30,38 @@
</domain:chg>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:rem>
<secDNS:dsData>
<secDNS:keyTag>7</secDNS:keyTag>
<secDNS:alg>8</secDNS:alg>
<secDNS:digestType>9</secDNS:digestType>
<secDNS:digest>12AB</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>6</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>4</secDNS:digestType>
<secDNS:digest>34CD</secDNS:digest>
</secDNS:dsData>
</secDNS:rem>
<secDNS:add>
<secDNS:dsData>
<secDNS:keyTag>1</secDNS:keyTag>
<secDNS:alg>2</secDNS:alg>
<secDNS:digestType>3</secDNS:digestType>
<secDNS:digest>ABCD</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>4</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>6</secDNS:digestType>
<secDNS:digest>EF01</secDNS:digest>
</secDNS:dsData>
</secDNS:add>
</secDNS:update>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -30,6 +30,38 @@
</domain:chg>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:rem>
<secDNS:dsData>
<secDNS:keyTag>7</secDNS:keyTag>
<secDNS:alg>8</secDNS:alg>
<secDNS:digestType>9</secDNS:digestType>
<secDNS:digest>12AB</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>6</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>4</secDNS:digestType>
<secDNS:digest>34CD</secDNS:digest>
</secDNS:dsData>
</secDNS:rem>
<secDNS:add>
<secDNS:dsData>
<secDNS:keyTag>1</secDNS:keyTag>
<secDNS:alg>2</secDNS:alg>
<secDNS:digestType>3</secDNS:digestType>
<secDNS:digest>ABCD</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>4</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>6</secDNS:digestType>
<secDNS:digest>EF01</secDNS:digest>
</secDNS:dsData>
</secDNS:add>
</secDNS:update>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -15,6 +15,24 @@
</domain:rem>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:rem>
<secDNS:dsData>
<secDNS:keyTag>7</secDNS:keyTag>
<secDNS:alg>8</secDNS:alg>
<secDNS:digestType>9</secDNS:digestType>
<secDNS:digest>12AB</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>6</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>4</secDNS:digestType>
<secDNS:digest>34CD</secDNS:digest>
</secDNS:dsData>
</secDNS:rem>
</secDNS:update>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:rem>
<secDNS:all>true</secDNS:all>
</secDNS:rem>
<secDNS:add>
<secDNS:dsData>
<secDNS:keyTag>1</secDNS:keyTag>
<secDNS:alg>2</secDNS:alg>
<secDNS:digestType>3</secDNS:digestType>
<secDNS:digest>ABCD</secDNS:digest>
</secDNS:dsData>
<secDNS:dsData>
<secDNS:keyTag>4</secDNS:keyTag>
<secDNS:alg>5</secDNS:alg>
<secDNS:digestType>6</secDNS:digestType>
<secDNS:digest>EF01</secDNS:digest>
</secDNS:dsData>
</secDNS:add>
</secDNS:update>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>