mirror of
https://github.com/google/nomulus.git
synced 2025-07-22 18:55:58 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
38
java/google/registry/tmch/BUILD
Normal file
38
java/google/registry/tmch/BUILD
Normal file
|
@ -0,0 +1,38 @@
|
|||
package(
|
||||
default_visibility = ["//java/com/google/domain/registry:registry_project"],
|
||||
)
|
||||
|
||||
|
||||
java_library(
|
||||
name = "tmch",
|
||||
srcs = glob(["*.java"]),
|
||||
resources = glob([
|
||||
"*.crl",
|
||||
"*.crt",
|
||||
"*.asc",
|
||||
]),
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/common/util/concurrent",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//java/com/google/domain/registry/cron",
|
||||
"//java/com/google/domain/registry/keyring/api",
|
||||
"//java/com/google/domain/registry/model",
|
||||
"//java/com/google/domain/registry/request",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//java/com/google/domain/registry/xml",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/bouncycastle",
|
||||
"//third_party/java/bouncycastle_bcpg",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/joda_time",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/jsr330_inject",
|
||||
"//third_party/java/objectify:objectify-v4_1",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
],
|
||||
)
|
80
java/google/registry/tmch/ClaimsListParser.java
Normal file
80
java/google/registry/tmch/ClaimsListParser.java
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.model.tmch.ClaimsListShard;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Claims List (MarksDB DNL CSV) Parser.
|
||||
*
|
||||
* <p>This is a quick and dirty CSV parser made specifically for the DNL CSV format defined in the
|
||||
* TMCH specification. It doesn't support any fancy CSV features like quotes.
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.1"
|
||||
*/
|
||||
public class ClaimsListParser {
|
||||
|
||||
/**
|
||||
* Converts the lines from the DNL CSV file into a {@link ClaimsListShard} object.
|
||||
*
|
||||
* <p>Please note that this does <b>not</b> insert the object into the datastore.
|
||||
*/
|
||||
public static ClaimsListShard parse(List<String> lines) {
|
||||
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
|
||||
|
||||
// First line: <version>,<DNL List creation datetime>
|
||||
List<String> firstLine = Splitter.on(',').splitToList(lines.get(0));
|
||||
checkArgument(firstLine.size() == 2, String.format(
|
||||
"Line 1: Expected 2 elements, found %d", firstLine.size()));
|
||||
|
||||
Integer version = Integer.valueOf(firstLine.get(0));
|
||||
DateTime creationTime = DateTime.parse(firstLine.get(1));
|
||||
checkArgument(version == 1, String.format(
|
||||
"Line 1: Expected version 1, found %d", version));
|
||||
|
||||
// Second line contains headers: DNL,lookup-key,insertion-datetime
|
||||
List<String> secondLine = Splitter.on(',').splitToList(lines.get(1));
|
||||
checkArgument(secondLine.size() == 3, String.format(
|
||||
"Line 2: Expected 3 elements, found %d", secondLine.size()));
|
||||
checkArgument("DNL".equals(secondLine.get(0)), String.format(
|
||||
"Line 2: Expected header \"DNL\", found \"%s\"", secondLine.get(0)));
|
||||
checkArgument("lookup-key".equals(secondLine.get(1)), String.format(
|
||||
"Line 2: Expected header \"lookup-key\", found \"%s\"", secondLine.get(1)));
|
||||
checkArgument("insertion-datetime".equals(secondLine.get(2)), String.format(
|
||||
"Line 2: Expected header \"insertion-datetime\", found \"%s\"", secondLine.get(2)));
|
||||
|
||||
// Subsequent lines: <DNL>,<lookup key>,<DNL insertion datetime>
|
||||
for (int i = 2; i < lines.size(); i++) {
|
||||
List<String> currentLine = Splitter.on(',').splitToList(lines.get(i));
|
||||
checkArgument(currentLine.size() == 3, String.format(
|
||||
"Line %d: Expected 3 elements, found %d", i + 1, currentLine.size()));
|
||||
|
||||
String label = currentLine.get(0);
|
||||
String lookupKey = currentLine.get(1);
|
||||
DateTime.parse(currentLine.get(2)); // This is the insertion time, currently unused.
|
||||
builder.put(label, lookupKey);
|
||||
}
|
||||
|
||||
return ClaimsListShard.create(creationTime, builder.build());
|
||||
}
|
||||
}
|
257
java/google/registry/tmch/LordnLog.java
Normal file
257
java/google/registry/tmch/LordnLog.java
Normal file
|
@ -0,0 +1,257 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Parser of LORDN log responses from the MarksDB server during the NORDN process.
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3.1"
|
||||
*/
|
||||
@Immutable
|
||||
public final class LordnLog implements Iterable<Entry<String, LordnLog.Result>> {
|
||||
|
||||
/** Indicates whether or not the LORDN upload succeeded. */
|
||||
public enum Status { ACCEPTED, REJECTED }
|
||||
|
||||
/** Result code for individual DN lines. */
|
||||
@Immutable
|
||||
public static final class Result {
|
||||
|
||||
/** Outcome categories for individual DN lines. */
|
||||
public enum Outcome { OK, WARNING, ERROR }
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
private final Outcome outcome;
|
||||
|
||||
private Result(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
if (2000 <= code && code <= 2099) {
|
||||
this.outcome = Outcome.OK;
|
||||
} else if (3500 <= code && code <= 3699) {
|
||||
this.outcome = Outcome.WARNING;
|
||||
} else if (4500 <= code && code <= 4699) {
|
||||
this.outcome = Outcome.ERROR;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid DN result code: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Outcome getOutcome() {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(this)
|
||||
.add("code", code)
|
||||
.add("outcome", outcome)
|
||||
.add("description", description)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Integer, Result> RESULTS = ImmutableMap.<Integer, Result>builder()
|
||||
.put(2000, new Result(2000, "OK"))
|
||||
.put(2001, new Result(2001, "OK but not processed"))
|
||||
.put(3601, new Result(3601, "TCN Acceptance Date after Registration Date"))
|
||||
.put(3602, new Result(3602, "Duplicate DN Line"))
|
||||
.put(3603, new Result(3603, "DNROID Notified Earlier"))
|
||||
.put(3604, new Result(3604, "TCN Checksum invalid"))
|
||||
.put(3605, new Result(3605, "TCN Expired"))
|
||||
.put(3606, new Result(3606, "Wrong TCNID used"))
|
||||
.put(3609, new Result(3609, "Invalid SMD used"))
|
||||
.put(3610, new Result(3610, "DN reported outside of the time window"))
|
||||
.put(3611, new Result(3611, "DN does not match the labels in SMD"))
|
||||
.put(3612, new Result(3612, "SMDID does not exist"))
|
||||
.put(3613, new Result(3613, "SMD was revoked when used"))
|
||||
.put(3614, new Result(3614, "TCNID does not exist"))
|
||||
.put(3615, new Result(3615, "Recent-dnl-insertion outside of the time window"))
|
||||
.put(3616, new Result(3616, "Registration Date of DN in claims before the end of Sunrise"))
|
||||
.put(3617, new Result(3617, "Registrar has not been approved by the TMDB"))
|
||||
.put(4501, new Result(4501, "Syntax Error in DN Line"))
|
||||
.put(4601, new Result(4601, "Invalid TLD used"))
|
||||
.put(4602, new Result(4602, "Registrar ID Invalid"))
|
||||
.put(4603, new Result(4603, "Registration Date in the future"))
|
||||
.put(4606, new Result(4606, "TLD not in Sunrise or Claims"))
|
||||
.put(4607, new Result(4607, "Application Date in the future"))
|
||||
.put(4608, new Result(4608, "Application Date is later than Registration Date"))
|
||||
.put(4609, new Result(4609, "TCNID wrong syntax"))
|
||||
.put(4610, new Result(4610, "TCN Acceptance Date is in the future"))
|
||||
.put(4611, new Result(4611, "Label has never existed in the TMDB"))
|
||||
.build();
|
||||
|
||||
/** Base64 matcher between one and sixty characters. */
|
||||
private static final Pattern LOG_ID_PATTERN = Pattern.compile("[-A-Za-z0-9+/=]{1,60}");
|
||||
|
||||
private final String logId;
|
||||
private final Status status;
|
||||
private final DateTime logCreation;
|
||||
private final DateTime lordnCreation;
|
||||
private final boolean hasWarnings;
|
||||
private final ImmutableMap<String, Result> results;
|
||||
|
||||
private LordnLog(
|
||||
String logId,
|
||||
Status status,
|
||||
DateTime logCreation,
|
||||
DateTime lordnCreation,
|
||||
boolean hasWarnings,
|
||||
ImmutableMap<String, Result> results) {
|
||||
this.logId = logId;
|
||||
this.status = status;
|
||||
this.logCreation = logCreation;
|
||||
this.lordnCreation = lordnCreation;
|
||||
this.hasWarnings = hasWarnings;
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public String getLogId() {
|
||||
return logId;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public DateTime getLogCreation() {
|
||||
return logCreation;
|
||||
}
|
||||
|
||||
public DateTime getLordnCreation() {
|
||||
return lordnCreation;
|
||||
}
|
||||
|
||||
public boolean hasWarnings() {
|
||||
return hasWarnings;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Result getResult(String roid) {
|
||||
return results.get(roid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, Result>> iterator() {
|
||||
return results.entrySet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(this)
|
||||
.add("logId", logId)
|
||||
.add("status", status)
|
||||
.add("logCreation", logCreation)
|
||||
.add("lordnCreation", lordnCreation)
|
||||
.add("hasWarnings", hasWarnings)
|
||||
.add("results", results)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/** Turns lines of NORDN log returned by MarksDB into a data structure. */
|
||||
public static LordnLog parse(List<String> lines) {
|
||||
// First line: <version>,<LORDN Log creation datetime>,<LORDN file creation datetime>,
|
||||
// <LORDN Log Identifier>,<Status flag>,<Warning flag>,<Number of DN Lines>
|
||||
List<String> firstLine = Splitter.on(',').splitToList(lines.get(0));
|
||||
checkArgument(firstLine.size() == 7, String.format(
|
||||
"Line 1: Expected 7 elements, found %d", firstLine.size()));
|
||||
|
||||
// + <version>, version of the file, this field MUST be 1.
|
||||
int version = Integer.parseInt(firstLine.get(0));
|
||||
checkArgument(version == 1, String.format(
|
||||
"Line 1: Expected version 1, found %d", version));
|
||||
|
||||
// + <LORDN Log creation datetime>, date and time in UTC that the
|
||||
// LORDN Log was created.
|
||||
DateTime logCreation = DateTime.parse(firstLine.get(1));
|
||||
|
||||
// + <LORDN file creation datetime>, date and time in UTC of
|
||||
// creation for the LORDN file that this log file is referring
|
||||
// to.
|
||||
DateTime lordnCreation = DateTime.parse(firstLine.get(2));
|
||||
|
||||
// + <LORDN Log Identifier>, unique identifier of the LORDN Log
|
||||
// provided by the TMDB. This identifier could be used by the
|
||||
// Registry Operator to unequivocally identify the LORDN Log.
|
||||
// The identified will be a string of a maximum LENGTH of 60
|
||||
// characters from the Base 64 alphabet.
|
||||
String logId = firstLine.get(3);
|
||||
checkArgument(LOG_ID_PATTERN.matcher(logId).matches(),
|
||||
"Line 1: Log ID does not match base64 pattern: %s", logId);
|
||||
|
||||
// + <Status flag>, whether the LORDN file has been accepted for
|
||||
// processing by the TMDB. Possible values are "accepted" or
|
||||
// "rejected".
|
||||
Status status = Status.valueOf(firstLine.get(4).toUpperCase());
|
||||
|
||||
// + <Warning flag>, whether the LORDN Log has any warning result
|
||||
// codes. Possible values are "no-warnings" or "warnings-
|
||||
// present".
|
||||
boolean hasWarnings = !"no-warnings".equals(firstLine.get(5));
|
||||
|
||||
// + <Number of DN Lines>, number of DNs effective allocations
|
||||
// processed in the LORDN file.
|
||||
int dnLines = Integer.parseInt(firstLine.get(6));
|
||||
int actual = lines.size() - 2;
|
||||
checkArgument(dnLines == actual,
|
||||
"Line 1: Number of entries (%d) differs from declaration (%d)", actual, dnLines);
|
||||
|
||||
// Second line contains headers: roid,result-code
|
||||
checkArgument(lines.get(1).equals("roid,result-code"),
|
||||
"Line 2: Unexpected header list: %s", lines.get(1));
|
||||
|
||||
// Subsequent lines: <roid>,<result code>
|
||||
ImmutableMap.Builder<String, Result> builder = new ImmutableMap.Builder<>();
|
||||
for (int i = 2; i < lines.size(); i++) {
|
||||
List<String> currentLine = Splitter.on(',').splitToList(lines.get(i));
|
||||
checkArgument(currentLine.size() == 2, String.format(
|
||||
"Line %d: Expected 2 elements, found %d", i + 1, currentLine.size()));
|
||||
String roid = currentLine.get(0);
|
||||
int code = Integer.parseInt(currentLine.get(1));
|
||||
Result result = checkNotNull(RESULTS.get(code),
|
||||
"Line %d: Unknown result code: %d", i, code);
|
||||
builder.put(roid, result);
|
||||
}
|
||||
|
||||
return new LordnLog(logId, status, logCreation, lordnCreation, hasWarnings, builder.build());
|
||||
}
|
||||
}
|
48
java/google/registry/tmch/LordnRequestInitializer.java
Normal file
48
java/google/registry/tmch/LordnRequestInitializer.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Verify.verifyNotNull;
|
||||
import static com.google.domain.registry.util.UrlFetchUtils.setAuthorizationHeader;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.keyring.api.KeyModule.Key;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Helper class for setting the authorization header on a MarksDB LORDN request. */
|
||||
final class LordnRequestInitializer {
|
||||
|
||||
@Inject @Key("marksdbLordnPassword") Optional<String> marksdbLordnPassword;
|
||||
@Inject LordnRequestInitializer() {}
|
||||
|
||||
/** Initializes a URL fetch request for talking to the MarksDB server. */
|
||||
void initialize(HTTPRequest request, String tld) {
|
||||
setAuthorizationHeader(request, getMarksDbLordnCredentials(tld));
|
||||
}
|
||||
|
||||
/** Returns the username and password for the current TLD to login to the MarksDB server. */
|
||||
private Optional<String> getMarksDbLordnCredentials(String tld) {
|
||||
if (marksdbLordnPassword.isPresent()) {
|
||||
String lordnUsername = verifyNotNull(Registry.get(tld).getLordnUsername(),
|
||||
"lordnUsername is not set for %s.", Registry.get(tld).getTld());
|
||||
return Optional.of(String.format("%s:%s", lordnUsername, marksdbLordnPassword.get()));
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
}
|
162
java/google/registry/tmch/LordnTask.java
Normal file
162
java/google/registry/tmch/LordnTask.java
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.appengine.api.taskqueue.LeaseOptions;
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions.Method;
|
||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
||||
import com.google.apphosting.api.DeadlineExceededException;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Helper methods for creating tasks containing CSV line data in the lordn-sunrise and lordn-claims
|
||||
* queues based on DomainResource changes.
|
||||
*/
|
||||
public class LordnTask {
|
||||
|
||||
public static final String QUEUE_SUNRISE = "lordn-sunrise";
|
||||
public static final String QUEUE_CLAIMS = "lordn-claims";
|
||||
public static final String COLUMNS_CLAIMS = "roid,domain-name,notice-id,registrar-id,"
|
||||
+ "registration-datetime,ack-datetime,application-datetime";
|
||||
public static final String COLUMNS_SUNRISE = "roid,domain-name,SMD-id,registrar-id,"
|
||||
+ "registration-datetime,application-datetime";
|
||||
private static final Duration LEASE_PERIOD = Duration.standardHours(1);
|
||||
|
||||
/** This is the max allowable batch size. */
|
||||
private static final long BATCH_SIZE = 1000;
|
||||
|
||||
@NonFinalForTesting
|
||||
private static Long backOffMillis = 2000L;
|
||||
|
||||
/**
|
||||
* Converts a list of queue tasks, each containing a row of CSV data, into a single newline-
|
||||
* delimited String.
|
||||
*/
|
||||
public static String convertTasksToCsv(List<TaskHandle> tasks, DateTime now, String columns) {
|
||||
String header = String.format("1,%s,%d\n%s\n", now, tasks.size(), columns);
|
||||
StringBuilder csv = new StringBuilder(header);
|
||||
for (TaskHandle task : checkNotNull(tasks)) {
|
||||
String payload = new String(task.getPayload());
|
||||
if (!Strings.isNullOrEmpty(payload)) {
|
||||
csv.append(payload).append("\n");
|
||||
}
|
||||
}
|
||||
return csv.toString();
|
||||
}
|
||||
|
||||
/** Leases and returns all tasks from the queue with the specified tag tld, in batches. */
|
||||
public static List<TaskHandle> loadAllTasks(Queue queue, String tld) {
|
||||
ImmutableList.Builder<TaskHandle> allTasks = new ImmutableList.Builder<>();
|
||||
int numErrors = 0;
|
||||
long backOff = backOffMillis;
|
||||
while (true) {
|
||||
try {
|
||||
List<TaskHandle> tasks = queue.leaseTasks(LeaseOptions.Builder
|
||||
.withTag(tld)
|
||||
.leasePeriod(LEASE_PERIOD.getMillis(), TimeUnit.MILLISECONDS)
|
||||
.countLimit(BATCH_SIZE));
|
||||
allTasks.addAll(tasks);
|
||||
if (tasks.isEmpty()) {
|
||||
return allTasks.build();
|
||||
}
|
||||
} catch (TransientFailureException | DeadlineExceededException e) {
|
||||
if (++numErrors >= 3) {
|
||||
throw new RuntimeException("Error leasing tasks", e);
|
||||
}
|
||||
Uninterruptibles.sleepUninterruptibly(backOff, TimeUnit.MILLISECONDS);
|
||||
backOff *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a task in the LORDN queue representing a line of CSV for LORDN export.
|
||||
*/
|
||||
public static void enqueueDomainResourceTask(DomainResource domain) {
|
||||
ofy().assertInTransaction();
|
||||
// This method needs to use ofy transactionTime as the DomainResource's creationTime because
|
||||
// CreationTime isn't yet populated when this method is called during the resource flow.
|
||||
String tld = domain.getTld();
|
||||
if (domain.getLaunchNotice() == null) {
|
||||
getQueue(QUEUE_SUNRISE).add(TaskOptions.Builder
|
||||
.withTag(tld)
|
||||
.method(Method.PULL)
|
||||
.payload(getCsvLineForSunriseDomain(domain, ofy().getTransactionTime())));
|
||||
} else {
|
||||
getQueue(QUEUE_CLAIMS).add(TaskOptions.Builder
|
||||
.withTag(tld)
|
||||
.method(Method.PULL)
|
||||
.payload(getCsvLineForClaimsDomain(domain, ofy().getTransactionTime())));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the corresponding CSV LORDN line for a sunrise domain. */
|
||||
public static String getCsvLineForSunriseDomain(DomainResource domain, DateTime transactionTime) {
|
||||
// Only skip nulls in the outer join because only application time is allowed to be null.
|
||||
Joiner joiner = Joiner.on(',');
|
||||
return joiner.skipNulls().join(
|
||||
joiner.join(
|
||||
domain.getRepoId(),
|
||||
domain.getFullyQualifiedDomainName(),
|
||||
domain.getSmdId(),
|
||||
getIanaIdentifier(domain.getCreationClientId()),
|
||||
transactionTime), // Used as creation time.
|
||||
domain.getApplicationTime()); // This may be null for sunrise QLP domains.
|
||||
}
|
||||
|
||||
/** Returns the corresponding CSV LORDN line for a claims domain. */
|
||||
public static String getCsvLineForClaimsDomain(DomainResource domain, DateTime transactionTime) {
|
||||
// Only skip nulls in the outer join because only application time is allowed to be null.
|
||||
Joiner joiner = Joiner.on(',');
|
||||
return joiner.skipNulls().join(
|
||||
joiner.join(
|
||||
domain.getRepoId(),
|
||||
domain.getFullyQualifiedDomainName(),
|
||||
domain.getLaunchNotice().getNoticeId().getTcnId(),
|
||||
getIanaIdentifier(domain.getCreationClientId()),
|
||||
transactionTime, // Used as creation time.
|
||||
domain.getLaunchNotice().getAcceptedTime()),
|
||||
domain.getApplicationTime()); // This may be null if this wasn't from landrush.
|
||||
}
|
||||
|
||||
/** Retrieves the IANA identifier for a registrar based on the client id. */
|
||||
private static String getIanaIdentifier(String clientId) {
|
||||
Registrar registrar = checkNotNull(
|
||||
Registrar.loadByClientId(clientId),
|
||||
"No registrar found for client id: %s", clientId);
|
||||
// Return the string "null" for null identifiers, since some Registrar.Types such as OTE will
|
||||
// have null iana ids.
|
||||
return String.valueOf(registrar.getIanaIdentifier());
|
||||
}
|
||||
}
|
129
java/google/registry/tmch/Marksdb.java
Normal file
129
java/google/registry/tmch/Marksdb.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.GET;
|
||||
import static com.google.domain.registry.util.HexDumper.dumpHex;
|
||||
import static com.google.domain.registry.util.UrlFetchUtils.setAuthorizationHeader;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.keyring.api.KeyModule.Key;
|
||||
import com.google.domain.registry.util.UrlFetchException;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Tainted;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Shared code for tasks that download stuff from MarksDB. */
|
||||
public final class Marksdb {
|
||||
|
||||
@Inject URLFetchService fetchService;
|
||||
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
|
||||
@Inject @Key("marksdbPublicKey") PGPPublicKey marksdbPublicKey;
|
||||
@Inject Marksdb() {}
|
||||
|
||||
/**
|
||||
* Extracts a {@link PGPSignature} object from a blob of {@code .sig} data.
|
||||
*
|
||||
* @throws SignatureException if a signature object couldn't be extracted for any reason.
|
||||
*/
|
||||
private static PGPSignature pgpExtractSignature(@Tainted byte[] signature)
|
||||
throws SignatureException {
|
||||
try {
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(signature);
|
||||
PGPObjectFactory decoder = new BcPGPObjectFactory(PGPUtil.getDecoderStream(input));
|
||||
Object object = decoder.nextObject();
|
||||
if (object == null) {
|
||||
throw new SignatureException(String.format(
|
||||
"No OpenPGP packets found in signature.\n%s",
|
||||
dumpHex(signature)));
|
||||
}
|
||||
if (!(object instanceof PGPSignatureList)) {
|
||||
throw new SignatureException(String.format(
|
||||
"Expected PGPSignatureList packet but got %s\n%s",
|
||||
object.getClass().getSimpleName(),
|
||||
dumpHex(signature)));
|
||||
}
|
||||
PGPSignatureList sigs = (PGPSignatureList) object;
|
||||
if (sigs.isEmpty()) {
|
||||
throw new SignatureException(String.format(
|
||||
"PGPSignatureList doesn't have a PGPSignature.\n%s",
|
||||
dumpHex(signature)));
|
||||
}
|
||||
return sigs.get(0);
|
||||
} catch (IOException e) {
|
||||
throw new SignatureException(String.format(
|
||||
"Failed to extract PGPSignature object from .sig blob.\n%s",
|
||||
dumpHex(signature)), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void pgpVerifySignature(byte[] data, byte[] signature, PGPPublicKey publicKey)
|
||||
throws PGPException, SignatureException {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
PGPSignature sig = pgpExtractSignature(signature);
|
||||
sig.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
|
||||
sig.update(data);
|
||||
if (!sig.verify()) {
|
||||
throw new SignatureException(String.format(
|
||||
"MarksDB PGP signature verification failed.\n%s",
|
||||
dumpHex(signature)));
|
||||
}
|
||||
}
|
||||
|
||||
byte[] fetch(URL url, Optional<String> login) throws IOException {
|
||||
HTTPRequest req = new HTTPRequest(url, GET, validateCertificate().setDeadline(60d));
|
||||
setAuthorizationHeader(req, login);
|
||||
HTTPResponse rsp = fetchService.fetch(req);
|
||||
if (rsp.getResponseCode() != SC_OK) {
|
||||
throw new UrlFetchException("Failed to fetch from MarksDB", req, rsp);
|
||||
}
|
||||
return rsp.getContent();
|
||||
}
|
||||
|
||||
List<String> fetchSignedCsv(
|
||||
Optional<String> login, String csvPath, String sigPath)
|
||||
throws IOException, SignatureException, PGPException {
|
||||
byte[] csv = fetch(new URL(tmchMarksdbUrl + csvPath), login);
|
||||
byte[] sig = fetch(new URL(tmchMarksdbUrl + sigPath), login);
|
||||
pgpVerifySignature(csv, sig, marksdbPublicKey);
|
||||
return ByteSource.wrap(csv).asCharSource(US_ASCII).readLines();
|
||||
}
|
||||
}
|
165
java/google/registry/tmch/NordnUploadAction.java
Normal file
165
java/google/registry/tmch/NordnUploadAction.java
Normal file
|
@ -0,0 +1,165 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.POST;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.CSV_UTF_8;
|
||||
import static com.google.domain.registry.tmch.LordnTask.COLUMNS_CLAIMS;
|
||||
import static com.google.domain.registry.tmch.LordnTask.COLUMNS_SUNRISE;
|
||||
import static com.google.domain.registry.tmch.LordnTask.convertTasksToCsv;
|
||||
import static com.google.domain.registry.util.UrlFetchUtils.getHeaderFirst;
|
||||
import static com.google.domain.registry.util.UrlFetchUtils.setPayloadMultipart;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
||||
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.request.RequestParameters;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.UrlFetchException;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Action that reads the NORDN pull queues, uploads claims and sunrise marks data to TMCH, and
|
||||
* enqueues subsequent upload verification tasks. A unique actionLogId is generated and passed
|
||||
* along to the verify action so that connected verify tasks can be identified by looking at logs.
|
||||
*
|
||||
* @see NordnVerifyAction
|
||||
*/
|
||||
@Action(path = NordnUploadAction.PATH, method = Action.Method.POST, automaticallyPrintOk = true)
|
||||
public final class NordnUploadAction implements Runnable {
|
||||
|
||||
static final String PATH = "/_dr/task/nordnUpload";
|
||||
static final String LORDN_PHASE_PARAM = "lordn-phase";
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/**
|
||||
* A unique (enough) id that is outputted in log lines to make it clear which log lines are
|
||||
* associated with a given invocation of the NordnUploadAction in the event that multiple
|
||||
* instances execute simultaneously.
|
||||
*/
|
||||
private final String actionLogId = String.valueOf(1000000000 + new Random().nextInt(1000000000));
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject LordnRequestInitializer lordnRequestInitializer;
|
||||
@Inject URLFetchService fetchService;
|
||||
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
|
||||
@Inject @Parameter(LORDN_PHASE_PARAM) String phase;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@Inject NordnUploadAction() {}
|
||||
|
||||
/**
|
||||
* These LORDN parameter names correspond to the relative paths in LORDN URLs and cannot be
|
||||
* changed on our end.
|
||||
*/
|
||||
private static final String PARAM_LORDN_PHASE_SUNRISE = "sunrise";
|
||||
private static final String PARAM_LORDN_PHASE_CLAIMS = "claims";
|
||||
|
||||
/** How long to wait before attempting to verify an upload by fetching the log. */
|
||||
private static final Duration VERIFY_DELAY = Duration.standardMinutes(30);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
processLordnTasks();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processLordnTasks() throws IOException {
|
||||
checkArgument(phase.equals(PARAM_LORDN_PHASE_SUNRISE)
|
||||
|| phase.equals(PARAM_LORDN_PHASE_CLAIMS),
|
||||
"Invalid phase specified to Nordn servlet: %s.", phase);
|
||||
DateTime now = clock.nowUtc();
|
||||
Queue queue = getQueue(
|
||||
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? LordnTask.QUEUE_SUNRISE : LordnTask.QUEUE_CLAIMS);
|
||||
String columns = phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
|
||||
List<TaskHandle> tasks = LordnTask.loadAllTasks(queue, tld);
|
||||
if (!tasks.isEmpty()) {
|
||||
String csvData = convertTasksToCsv(tasks, now, columns);
|
||||
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), csvData);
|
||||
queue.deleteTask(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload LORDN file to MarksDB.
|
||||
*
|
||||
* <p>Idempotency: If the exact same LORDN report is uploaded twice, the MarksDB server will
|
||||
* return the same confirmation number.
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3"
|
||||
*/
|
||||
private void uploadCsvToLordn(String urlPath, String csvData) throws IOException {
|
||||
String url = tmchMarksdbUrl + urlPath;
|
||||
logger.infofmt("LORDN upload task %s: Sending to URL: %s ; data: %s",
|
||||
actionLogId, url, csvData);
|
||||
HTTPRequest req = new HTTPRequest(new URL(url), POST, validateCertificate().setDeadline(60d));
|
||||
lordnRequestInitializer.initialize(req, tld);
|
||||
setPayloadMultipart(req, "file", "claims.csv", CSV_UTF_8, csvData);
|
||||
HTTPResponse rsp = fetchService.fetch(req);
|
||||
logger.infofmt("LORDN upload task %s response: HTTP response code %d, response data: %s",
|
||||
actionLogId, rsp.getResponseCode(), rsp.getContent());
|
||||
if (rsp.getResponseCode() != SC_ACCEPTED) {
|
||||
throw new UrlFetchException(
|
||||
String.format("LORDN upload task %s error: Failed to upload LORDN claims to MarksDB",
|
||||
actionLogId),
|
||||
req, rsp);
|
||||
}
|
||||
Optional<String> location = getHeaderFirst(rsp, LOCATION);
|
||||
if (!location.isPresent()) {
|
||||
throw new UrlFetchException(
|
||||
String.format("LORDN upload task %s error: MarksDB failed to provide a Location header",
|
||||
actionLogId),
|
||||
req, rsp);
|
||||
}
|
||||
getQueue(NordnVerifyAction.QUEUE).add(makeVerifyTask(new URL(location.get()), csvData));
|
||||
}
|
||||
|
||||
private TaskOptions makeVerifyTask(URL url, String csvData) {
|
||||
// This task doesn't technically need csvData. The only reason it's passed along is in case the
|
||||
// upload is rejected, in which case csvData will be logged so that it may be uploaded manually.
|
||||
return withUrl(NordnVerifyAction.PATH)
|
||||
.header(NordnVerifyAction.URL_HEADER, url.toString())
|
||||
.header(NordnVerifyAction.HEADER_ACTION_LOG_ID, actionLogId)
|
||||
.param(RequestParameters.PARAM_TLD, tld)
|
||||
.param(NordnVerifyAction.PARAM_CSV_DATA, csvData)
|
||||
.countdownMillis(VERIFY_DELAY.getMillis());
|
||||
}
|
||||
}
|
141
java/google/registry/tmch/NordnVerifyAction.java
Normal file
141
java/google/registry/tmch/NordnVerifyAction.java
Normal file
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
||||
import static com.google.appengine.api.urlfetch.HTTPMethod.GET;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.appengine.api.urlfetch.URLFetchService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.Header;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.ConflictException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.request.RequestParameters;
|
||||
import com.google.domain.registry.request.Response;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.UrlFetchException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* NORDN CSV uploading system, verify operation.
|
||||
*
|
||||
* <p>Every three hours (max twenty-six hours) we generate CSV files for each TLD which we need
|
||||
* to upload to MarksDB. The upload is a two-phase process. We send the CSV data as a POST request
|
||||
* and get back a 202 Accepted. This response will give us a URL in the Location header, where
|
||||
* we'll check back later for the actual result.
|
||||
*
|
||||
* @see NordnUploadAction
|
||||
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-5.2.3.3">
|
||||
* http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-5.2.3.3</a>
|
||||
*/
|
||||
@Action(path = NordnVerifyAction.PATH, method = Action.Method.POST, automaticallyPrintOk = true)
|
||||
public final class NordnVerifyAction implements Runnable {
|
||||
|
||||
public static final String PARAM_CSV_DATA = "csvData";
|
||||
|
||||
static final String PATH = "/_dr/task/nordnVerify";
|
||||
static final String QUEUE = "marksdb";
|
||||
static final String URL_HEADER = "X-DomainRegistry-Nordn-Url";
|
||||
static final String HEADER_ACTION_LOG_ID = "X-DomainRegistry-ActionLogId";
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@Inject LordnRequestInitializer lordnRequestInitializer;
|
||||
@Inject Response response;
|
||||
@Inject URLFetchService fetchService;
|
||||
@Inject @Header(URL_HEADER) URL url;
|
||||
@Inject @Header(HEADER_ACTION_LOG_ID) String actionLogId;
|
||||
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
||||
@Inject @Parameter(PARAM_CSV_DATA) String csvData;
|
||||
@Inject NordnVerifyAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
verify();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch LORDN log file from MarksDB to confirm successful upload.
|
||||
*
|
||||
* <p>Idempotency: The confirmation URL will always return the same result once it becomes
|
||||
* available.
|
||||
*
|
||||
* @throws ConflictException if MarksDB has not yet finished processing the LORDN upload
|
||||
* @see "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3.1"
|
||||
*/
|
||||
@VisibleForTesting
|
||||
LordnLog verify() throws IOException {
|
||||
if (csvData.isEmpty()) {
|
||||
throw new BadRequestException(
|
||||
String.format("LORDN verify task %s: Missing CSV payload.", actionLogId));
|
||||
}
|
||||
logger.infofmt("LORDN verify task %s: Sending request to URL %s.", actionLogId, url);
|
||||
HTTPRequest req = new HTTPRequest(url, GET, validateCertificate().setDeadline(60d));
|
||||
lordnRequestInitializer.initialize(req, tld);
|
||||
HTTPResponse rsp = fetchService.fetch(req);
|
||||
logger.infofmt("LORDN verify task %s response: HTTP response code %d, response data: %s",
|
||||
actionLogId, rsp.getResponseCode(), rsp.getContent());
|
||||
if (rsp.getResponseCode() == SC_NO_CONTENT) {
|
||||
// Send a 400+ status code so App Engine will retry the task.
|
||||
throw new ConflictException("Not ready");
|
||||
}
|
||||
if (rsp.getResponseCode() != SC_OK) {
|
||||
throw new UrlFetchException(
|
||||
String.format("LORDN verify task %s: Failed to verify LORDN upload to MarksDB.",
|
||||
actionLogId),
|
||||
req, rsp);
|
||||
}
|
||||
LordnLog log =
|
||||
LordnLog.parse(ByteSource.wrap(rsp.getContent()).asCharSource(UTF_8).readLines());
|
||||
if (log.getStatus() == LordnLog.Status.ACCEPTED) {
|
||||
logger.infofmt("LORDN verify task %s: Upload accepted", actionLogId);
|
||||
} else {
|
||||
logger.severefmt("LORDN verify task %s: Upload rejected with reason: %s", actionLogId, log);
|
||||
}
|
||||
for (Entry<String, LordnLog.Result> result : log) {
|
||||
switch (result.getValue().getOutcome()) {
|
||||
case OK:
|
||||
break;
|
||||
case WARNING:
|
||||
// fall through
|
||||
case ERROR:
|
||||
logger.warning(result.toString());
|
||||
break;
|
||||
default:
|
||||
logger.warningfmt("LORDN verify task %s: Unexpected outcome: %s",
|
||||
actionLogId, result.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return log;
|
||||
}
|
||||
}
|
72
java/google/registry/tmch/SmdrlCsvParser.java
Normal file
72
java/google/registry/tmch/SmdrlCsvParser.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.model.smd.SignedMarkRevocationList;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Signed Mark Data Revocation List (SMDRL) CSV Parser
|
||||
*
|
||||
* <p>This is a quick and dirty CSV parser made specifically for the SMDRL CSV format defined in
|
||||
* the TMCH specification. It doesn't support any fancy CSV features like quotes.
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.2"
|
||||
*/
|
||||
public final class SmdrlCsvParser {
|
||||
|
||||
/** Converts the lines from the DNL CSV file into a data structure. */
|
||||
public static SignedMarkRevocationList parse(List<String> lines) {
|
||||
ImmutableMap.Builder<String, DateTime> revokes = new ImmutableMap.Builder<>();
|
||||
|
||||
// First line: <version>,<SMD Revocation List creation datetime>
|
||||
List<String> firstLine = Splitter.on(',').splitToList(lines.get(0));
|
||||
checkArgument(firstLine.size() == 2, String.format(
|
||||
"Line 1: Expected 2 elements, found %d", firstLine.size()));
|
||||
Integer version = Integer.valueOf(firstLine.get(0));
|
||||
checkArgument(version == 1, String.format(
|
||||
"Line 1: Expected version 1, found %d", version));
|
||||
DateTime creationTime = DateTime.parse(firstLine.get(1)).withZone(UTC);
|
||||
|
||||
// Second line contains headers: smd-id,insertion-datetime
|
||||
List<String> secondLine = Splitter.on(',').splitToList(lines.get(1));
|
||||
checkArgument(secondLine.size() == 2, String.format(
|
||||
"Line 2: Expected 2 elements, found %d", secondLine.size()));
|
||||
checkArgument("smd-id".equals(secondLine.get(0)), String.format(
|
||||
"Line 2: Expected header \"smd-id\", found \"%s\"", secondLine.get(0)));
|
||||
checkArgument("insertion-datetime".equals(secondLine.get(1)), String.format(
|
||||
"Line 2: Expected header \"insertion-datetime\", found \"%s\"", secondLine.get(1)));
|
||||
|
||||
// Subsequent lines: <smd-id>,<revoked SMD datetime>
|
||||
for (int i = 2; i < lines.size(); i++) {
|
||||
List<String> currentLine = Splitter.on(',').splitToList(lines.get(i));
|
||||
checkArgument(currentLine.size() == 2, String.format(
|
||||
"Line %d: Expected 2 elements, found %d", i + 1, currentLine.size()));
|
||||
String smdId = currentLine.get(0);
|
||||
DateTime revokedTime = DateTime.parse(currentLine.get(1));
|
||||
revokes.put(smdId, revokedTime);
|
||||
}
|
||||
|
||||
return SignedMarkRevocationList.create(creationTime, revokes.build());
|
||||
}
|
||||
}
|
143
java/google/registry/tmch/TmchCertificateAuthority.java
Normal file
143
java/google/registry/tmch/TmchCertificateAuthority.java
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Throwables.propagateIfInstanceOf;
|
||||
import static com.google.domain.registry.util.CacheUtils.memoizeWithLongExpiration;
|
||||
import static com.google.domain.registry.util.CacheUtils.memoizeWithShortExpiration;
|
||||
import static com.google.domain.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import static com.google.domain.registry.util.X509Utils.loadCrl;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.model.tmch.TmchCrl;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
import com.google.domain.registry.util.SystemClock;
|
||||
import com.google.domain.registry.util.X509Utils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/** Datastore singleton for ICANN's TMCH root certificate and revocation list. */
|
||||
@Immutable
|
||||
@ThreadSafe
|
||||
public final class TmchCertificateAuthority {
|
||||
|
||||
private static final RegistryEnvironment ENVIRONMENT = RegistryEnvironment.get();
|
||||
|
||||
private static final String ROOT_CRT_FILE = "icann-tmch.crt";
|
||||
private static final String TEST_ROOT_CRT_FILE = "icann-tmch-test.crt";
|
||||
private static final String CRL_FILE = "icann-tmch.crl";
|
||||
private static final String TEST_CRL_FILE = "icann-tmch-test.crl";
|
||||
|
||||
/**
|
||||
* A cached supplier that loads the crl from datastore or chooses a default value.
|
||||
* <p>
|
||||
* We keep the cache here rather than caching TmchCrl in the model, because loading the crl string
|
||||
* into an X509CRL instance is expensive and should itself be cached.
|
||||
*/
|
||||
private static final Supplier<X509CRL> CRL_CACHE =
|
||||
memoizeWithShortExpiration(new Supplier<X509CRL>() {
|
||||
@Override
|
||||
public X509CRL get() {
|
||||
TmchCrl storedCrl = TmchCrl.get();
|
||||
try {
|
||||
X509CRL crl = loadCrl((storedCrl == null)
|
||||
? readResourceUtf8(
|
||||
TmchCertificateAuthority.class,
|
||||
ENVIRONMENT.config().getTmchCaTestingMode() ? TEST_CRL_FILE : CRL_FILE)
|
||||
: storedCrl.getCrl());
|
||||
crl.verify(getRoot().getPublicKey());
|
||||
return crl;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}});
|
||||
|
||||
/** A cached function that loads the crt from a jar resource. */
|
||||
private static final Supplier<X509Certificate> ROOT_CACHE =
|
||||
memoizeWithLongExpiration(new Supplier<X509Certificate>() {
|
||||
@Override
|
||||
public X509Certificate get() {
|
||||
try {
|
||||
X509Certificate root = X509Utils.loadCertificate(readResourceUtf8(
|
||||
TmchCertificateAuthority.class,
|
||||
ENVIRONMENT.config().getTmchCaTestingMode() ? TEST_ROOT_CRT_FILE : ROOT_CRT_FILE));
|
||||
root.checkValidity(clock.nowUtc().toDate());
|
||||
return root;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}});
|
||||
|
||||
@NonFinalForTesting
|
||||
private static Clock clock = new SystemClock();
|
||||
|
||||
/**
|
||||
* Check that {@code cert} is signed by the ICANN TMCH CA root and not revoked.
|
||||
*
|
||||
* <p>Support for certificate chains has not been implemented.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @see X509Utils#verifyCertificate
|
||||
*/
|
||||
public static void verify(X509Certificate cert) throws GeneralSecurityException {
|
||||
synchronized (TmchCertificateAuthority.class) {
|
||||
X509Utils.verifyCertificate(getRoot(), getCrl(), cert, clock.nowUtc().toDate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update to the latest TMCH X.509 certificate revocation list and save to the datastore.
|
||||
*
|
||||
* <p>Your ASCII-armored CRL must be signed by the current ICANN root certificate.
|
||||
*
|
||||
* <p>This will not take effect (either on this instance or on others) until the CRL_CACHE next
|
||||
* refreshes itself.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @see X509Utils#verifyCrl
|
||||
*/
|
||||
public static void updateCrl(String asciiCrl) throws GeneralSecurityException {
|
||||
X509CRL crl = X509Utils.loadCrl(asciiCrl);
|
||||
X509Utils.verifyCrl(getRoot(), getCrl(), crl, clock.nowUtc().toDate());
|
||||
TmchCrl.set(asciiCrl);
|
||||
}
|
||||
|
||||
public static X509Certificate getRoot() throws GeneralSecurityException {
|
||||
try {
|
||||
return ROOT_CACHE.get();
|
||||
} catch (RuntimeException e) {
|
||||
propagateIfInstanceOf(e.getCause(), GeneralSecurityException.class);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static X509CRL getCrl() throws GeneralSecurityException {
|
||||
try {
|
||||
return CRL_CACHE.get();
|
||||
} catch (RuntimeException e) {
|
||||
propagateIfInstanceOf(e.getCause(), GeneralSecurityException.class);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
48
java/google/registry/tmch/TmchCrlAction.java
Normal file
48
java/google/registry/tmch/TmchCrlAction.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.domain.registry.request.Action.Method.POST;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.request.Action;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Action to download the latest ICANN TMCH CRL from MarksDB. */
|
||||
@Action(path = "/_dr/task/tmchCrl", method = POST, automaticallyPrintOk = true)
|
||||
public final class TmchCrlAction implements Runnable {
|
||||
|
||||
@Inject Marksdb marksdb;
|
||||
@Inject @Config("tmchCrlUrl") URL tmchCrlUrl;
|
||||
@Inject TmchCrlAction() {}
|
||||
|
||||
/** Synchronously fetches latest ICANN TMCH CRL and saves it to datastore. */
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
TmchCertificateAuthority
|
||||
.updateCrl(new String(marksdb.fetch(tmchCrlUrl, Optional.<String>absent()), UTF_8));
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException("Failed to update ICANN TMCH CRL.", e);
|
||||
}
|
||||
}
|
||||
}
|
54
java/google/registry/tmch/TmchData.java
Normal file
54
java/google/registry/tmch/TmchData.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.CharMatcher.whitespace;
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.domain.registry.model.smd.EncodedSignedMark;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/** Helper class for common data loaded from the jar and datastore at runtime. */
|
||||
public final class TmchData {
|
||||
|
||||
private static final String BEGIN_ENCODED_SMD = "-----BEGIN ENCODED SMD-----";
|
||||
private static final String END_ENCODED_SMD = "-----END ENCODED SMD-----";
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static PGPPublicKey loadPublicKey(ByteSource pgpPublicKeyFile) {
|
||||
try (InputStream input = pgpPublicKeyFile.openStream();
|
||||
InputStream decoder = PGPUtil.getDecoderStream(input)) {
|
||||
return new BcPGPPublicKeyRing(decoder).getPublicKey();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Extracts encoded SMD from an ASCII-armored string. */
|
||||
public static EncodedSignedMark readEncodedSignedMark(String data) {
|
||||
int beginTagIndex = data.indexOf(BEGIN_ENCODED_SMD);
|
||||
int endTagIndex = data.indexOf(END_ENCODED_SMD);
|
||||
if (beginTagIndex >= 0 && endTagIndex >= 0) {
|
||||
data = data.substring(beginTagIndex + BEGIN_ENCODED_SMD.length(), endTagIndex);
|
||||
}
|
||||
return EncodedSignedMark.create("base64", whitespace().removeFrom(data));
|
||||
}
|
||||
}
|
59
java/google/registry/tmch/TmchDnlAction.java
Normal file
59
java/google/registry/tmch/TmchDnlAction.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.domain.registry.request.Action.Method.POST;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.keyring.api.KeyModule.Key;
|
||||
import com.google.domain.registry.model.tmch.ClaimsListShard;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Action to download the latest domain name list (aka claims list) from MarksDB. */
|
||||
@Action(path = "/_dr/task/tmchDnl", method = POST, automaticallyPrintOk = true)
|
||||
public final class TmchDnlAction implements Runnable {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
private static final String DNL_CSV_PATH = "/dnl/dnl-latest.csv";
|
||||
private static final String DNL_SIG_PATH = "/dnl/dnl-latest.sig";
|
||||
|
||||
@Inject Marksdb marksdb;
|
||||
@Inject @Key("marksdbDnlLogin") Optional<String> marksdbDnlLogin;
|
||||
@Inject TmchDnlAction() {}
|
||||
|
||||
/** Synchronously fetches latest domain name list and saves it to datastore. */
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> lines;
|
||||
try {
|
||||
lines = marksdb.fetchSignedCsv(marksdbDnlLogin, DNL_CSV_PATH, DNL_SIG_PATH);
|
||||
} catch (SignatureException | IOException | PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ClaimsListShard claims = ClaimsListParser.parse(lines);
|
||||
claims.save();
|
||||
logger.infofmt("Inserted %,d claims into datastore, created at %s",
|
||||
claims.size(), claims.getCreationTime());
|
||||
}
|
||||
}
|
77
java/google/registry/tmch/TmchModule.java
Normal file
77
java/google/registry/tmch/TmchModule.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.io.Resources.asByteSource;
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static com.google.domain.registry.request.RequestParameters.extractRequiredHeader;
|
||||
import static com.google.domain.registry.request.RequestParameters.extractRequiredParameter;
|
||||
|
||||
import com.google.domain.registry.keyring.api.KeyModule.Key;
|
||||
import com.google.domain.registry.request.Header;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Dagger module for TMCH package. */
|
||||
@Module
|
||||
public final class TmchModule {
|
||||
|
||||
private static final PGPPublicKey MARKSDB_PUBLIC_KEY = TmchData
|
||||
.loadPublicKey(asByteSource(getResource(TmchModule.class, "marksdb-public-key.asc")));
|
||||
|
||||
@Provides
|
||||
@Key("marksdbPublicKey")
|
||||
static PGPPublicKey getMarksdbPublicKey() {
|
||||
return MARKSDB_PUBLIC_KEY;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(NordnUploadAction.LORDN_PHASE_PARAM)
|
||||
static String provideLordnPhase(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, NordnUploadAction.LORDN_PHASE_PARAM);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Header(NordnVerifyAction.URL_HEADER)
|
||||
static URL provideUrl(HttpServletRequest req) {
|
||||
try {
|
||||
return new URL(extractRequiredHeader(req, NordnVerifyAction.URL_HEADER));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new BadRequestException("Bad URL: " + NordnVerifyAction.URL_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Header(NordnVerifyAction.HEADER_ACTION_LOG_ID)
|
||||
static String provideActionLogId(HttpServletRequest req) {
|
||||
return extractRequiredHeader(req, NordnVerifyAction.HEADER_ACTION_LOG_ID);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(NordnVerifyAction.PARAM_CSV_DATA)
|
||||
static String provideCsvData(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, NordnVerifyAction.PARAM_CSV_DATA);
|
||||
}
|
||||
}
|
59
java/google/registry/tmch/TmchSmdrlAction.java
Normal file
59
java/google/registry/tmch/TmchSmdrlAction.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.domain.registry.request.Action.Method.POST;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.keyring.api.KeyModule.Key;
|
||||
import com.google.domain.registry.model.smd.SignedMarkRevocationList;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Action to download the latest signed mark revocation list from MarksDB. */
|
||||
@Action(path = "/_dr/task/tmchSmdrl", method = POST, automaticallyPrintOk = true)
|
||||
public final class TmchSmdrlAction implements Runnable {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
private static final String SMDRL_CSV_PATH = "/smdrl/smdrl-latest.csv";
|
||||
private static final String SMDRL_SIG_PATH = "/smdrl/smdrl-latest.sig";
|
||||
|
||||
@Inject Marksdb marksdb;
|
||||
@Inject @Key("marksdbSmdrlLogin") Optional<String> marksdbSmdrlLogin;
|
||||
@Inject TmchSmdrlAction() {}
|
||||
|
||||
/** Synchronously fetches latest signed mark revocation list and saves it to datastore. */
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> lines;
|
||||
try {
|
||||
lines = marksdb.fetchSignedCsv(marksdbSmdrlLogin, SMDRL_CSV_PATH, SMDRL_SIG_PATH);
|
||||
} catch (SignatureException | IOException | PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SignedMarkRevocationList smdrl = SmdrlCsvParser.parse(lines);
|
||||
smdrl.save();
|
||||
logger.infofmt("Inserted %,d smd revocations into datastore, created at %s",
|
||||
smdrl.size(), smdrl.getCreationTime());
|
||||
}
|
||||
}
|
192
java/google/registry/tmch/TmchXmlSignature.java
Normal file
192
java/google/registry/tmch/TmchXmlSignature.java
Normal file
|
@ -0,0 +1,192 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.domain.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Throwables.getRootCause;
|
||||
import static com.google.common.base.Throwables.propagateIfInstanceOf;
|
||||
import static com.google.domain.registry.xml.XmlTransformer.loadXmlSchemas;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.xml.crypto.AlgorithmMethod;
|
||||
import javax.xml.crypto.KeySelector;
|
||||
import javax.xml.crypto.KeySelectorException;
|
||||
import javax.xml.crypto.KeySelectorResult;
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.XMLCryptoContext;
|
||||
import javax.xml.crypto.dsig.Reference;
|
||||
import javax.xml.crypto.dsig.XMLSignature;
|
||||
import javax.xml.crypto.dsig.XMLSignatureException;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
|
||||
import javax.xml.crypto.dsig.keyinfo.X509Data;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.validation.Schema;
|
||||
|
||||
/** Helper class for verifying TMCH certificates and XML signatures. */
|
||||
@ThreadSafe
|
||||
public final class TmchXmlSignature {
|
||||
|
||||
private static final Schema SCHEMA =
|
||||
loadXmlSchemas(ImmutableList.of("mark.xsd", "dsig.xsd", "smd.xsd"));
|
||||
|
||||
/**
|
||||
* Verifies that signed mark data contains a valid signature.
|
||||
*
|
||||
* <p>This method DOES NOT check if the SMD ID is revoked. It's only concerned with the
|
||||
* cryptographic stuff.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @throws IOException
|
||||
* @throws MarshalException
|
||||
* @throws ParserConfigurationException
|
||||
* @throws SAXException
|
||||
*/
|
||||
public static void verify(byte[] smdXml)
|
||||
throws GeneralSecurityException,
|
||||
IOException,
|
||||
MarshalException,
|
||||
ParserConfigurationException,
|
||||
SAXException,
|
||||
XMLSignatureException {
|
||||
checkArgument(smdXml.length > 0);
|
||||
Document doc = parseSmdDocument(new ByteArrayInputStream(smdXml));
|
||||
|
||||
NodeList signatureNodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||
if (signatureNodes.getLength() != 1) {
|
||||
throw new XMLSignatureException("Expected exactly one <ds:Signature> element.");
|
||||
}
|
||||
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
|
||||
KeyValueKeySelector selector = new KeyValueKeySelector();
|
||||
DOMValidateContext context = new DOMValidateContext(selector, signatureNodes.item(0));
|
||||
XMLSignature signature = factory.unmarshalXMLSignature(context);
|
||||
|
||||
boolean isValid;
|
||||
try {
|
||||
isValid = signature.validate(context);
|
||||
} catch (XMLSignatureException e) {
|
||||
Throwable cause = getRootCause(e);
|
||||
propagateIfInstanceOf(cause, GeneralSecurityException.class);
|
||||
throw e;
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new XMLSignatureException(explainValidationProblem(context, signature));
|
||||
}
|
||||
}
|
||||
|
||||
private static Document parseSmdDocument(InputStream input)
|
||||
throws SAXException, IOException, ParserConfigurationException {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setSchema(SCHEMA);
|
||||
dbf.setAttribute("http://apache.org/xml/features/validation/schema/normalized-value", false);
|
||||
dbf.setNamespaceAware(true);
|
||||
return dbf.newDocumentBuilder().parse(input);
|
||||
}
|
||||
|
||||
private static String explainValidationProblem(
|
||||
DOMValidateContext context, XMLSignature signature)
|
||||
throws XMLSignatureException {
|
||||
@SuppressWarnings("unchecked") // Safe by specification.
|
||||
List<Reference> references = signature.getSignedInfo().getReferences();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Signature failed core validation\n");
|
||||
boolean sv = signature.getSignatureValue().validate(context);
|
||||
builder.append("Signature validation status: " + sv + "\n");
|
||||
for (Reference ref : references) {
|
||||
builder.append("references[");
|
||||
builder.append(ref.getURI());
|
||||
builder.append("] validity status: ");
|
||||
builder.append(ref.validate(context));
|
||||
builder.append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/** Callback class for DOM validator checks validity of {@code <ds:KeyInfo>} elements. */
|
||||
private static final class KeyValueKeySelector extends KeySelector {
|
||||
@Nullable
|
||||
@Override
|
||||
public KeySelectorResult select(
|
||||
@Nullable KeyInfo keyInfo,
|
||||
KeySelector.Purpose purpose,
|
||||
AlgorithmMethod method,
|
||||
XMLCryptoContext context) throws KeySelectorException {
|
||||
if (keyInfo == null) {
|
||||
return null;
|
||||
}
|
||||
for (Object keyInfoChild : keyInfo.getContent()) {
|
||||
if (keyInfoChild instanceof X509Data) {
|
||||
X509Data x509Data = (X509Data) keyInfoChild;
|
||||
for (Object x509DataChild : x509Data.getContent()) {
|
||||
if (x509DataChild instanceof X509Certificate) {
|
||||
X509Certificate cert = (X509Certificate) x509DataChild;
|
||||
try {
|
||||
TmchCertificateAuthority.verify(cert);
|
||||
} catch (SignatureException e) {
|
||||
throw new KeySelectorException(new CertificateSignatureException(e.getMessage()));
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new KeySelectorException(e);
|
||||
}
|
||||
return new SimpleKeySelectorResult(cert.getPublicKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new KeySelectorException("No public key found.");
|
||||
}
|
||||
}
|
||||
|
||||
/** @see TmchXmlSignature.KeyValueKeySelector */
|
||||
private static class SimpleKeySelectorResult implements KeySelectorResult {
|
||||
private PublicKey publicKey;
|
||||
|
||||
SimpleKeySelectorResult(PublicKey publicKey) {
|
||||
this.publicKey = checkNotNull(publicKey, "publicKey");
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.security.Key getKey() {
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
|
||||
/** CertificateException wrapper. */
|
||||
public static class CertificateSignatureException extends CertificateException {
|
||||
public CertificateSignatureException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
15
java/google/registry/tmch/icann-tmch-test.crl
Normal file
15
java/google/registry/tmch/icann-tmch-test.crl
Normal file
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN X509 CRL-----
|
||||
MIICVDCCATwCAQEwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxPDA6BgNV
|
||||
BAoTM0ludGVybmV0IENvcnBvcmF0aW9uIGZvciBBc3NpZ25lZCBOYW1lcyBhbmQg
|
||||
TnVtYmVyczEvMC0GA1UEAxMmSUNBTk4gVHJhZGVtYXJrIENsZWFyaW5naG91c2Ug
|
||||
UGlsb3QgQ0EXDTEzMDcwOTAwMDAwMFoXDTE4MDYyNTIzNTk1OVowWzBZAiAusBt6
|
||||
+hp7nbLd/oLa0HRKfentcmbRPTYWiDEoJ82FexcNMTMwNzA5MjIwMzIwWjAmMAoG
|
||||
A1UdFQQDCgEBMBgGA1UdGAQRGA8yMDEzMDcwOTIxMjcwMFqgLzAtMB8GA1UdIwQY
|
||||
MBaAFMOtPqbWEQBFgFw6V0qKbdwxDZ5xMAoGA1UdFAQDAgECMA0GCSqGSIb3DQEB
|
||||
CwUAA4IBAQCa3ZHr/qihqZ/M6Eo9SQ2G2dkvT6cs1L71YkiGmCpZdWvYm99sJ3yj
|
||||
iLe00vEyLWCPcq/qmgmhM0+Ou8ZHZ2nw2f4mzkjRwpzUn8oNMd5FHwlhpcRXHyjH
|
||||
DhPbX1a5xITPZj9UUq0Lhk+bciCtC+G/jtEIuaIWL5bW1KER+FxI3Tt3888xx17W
|
||||
0QoQiXEHltjl9zTj09YiVb4usGOQysLzAbhnyG5IBDBn11gWwx/g7rcXFO/z0KIp
|
||||
h3rKk5noar/kpp/qAzNDyByMfD2uJtHnxHLrafsK3HY6CDLCp6GqLNSA5zxSjVOq
|
||||
3sEaZYxI0Fg5DqBuN7efSCbM9bwFTYMy
|
||||
-----END X509 CRL-----
|
26
java/google/registry/tmch/icann-tmch-test.crt
Normal file
26
java/google/registry/tmch/icann-tmch-test.crt
Normal file
|
@ -0,0 +1,26 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEVjCCAz6gAwIBAgIgLrAbevoae52y3f6C2tB0Sn3p7XJm0T02FogxKCfNhXkw
|
||||
DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxPDA6BgNVBAoTM0ludGVybmV0
|
||||
IENvcnBvcmF0aW9uIGZvciBBc3NpZ25lZCBOYW1lcyBhbmQgTnVtYmVyczEvMC0G
|
||||
A1UEAxMmSUNBTk4gVHJhZGVtYXJrIENsZWFyaW5naG91c2UgUGlsb3QgQ0EwHhcN
|
||||
MTMwNjI2MDAwMDAwWhcNMjMwNjI1MjM1OTU5WjB8MQswCQYDVQQGEwJVUzE8MDoG
|
||||
A1UEChMzSW50ZXJuZXQgQ29ycG9yYXRpb24gZm9yIEFzc2lnbmVkIE5hbWVzIGFu
|
||||
ZCBOdW1iZXJzMS8wLQYDVQQDEyZJQ0FOTiBUcmFkZW1hcmsgQ2xlYXJpbmdob3Vz
|
||||
ZSBQaWxvdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMJiRqFg
|
||||
iCoDF8zMJMKHPMEuSpjbEl9ZWII+1WawDyt+jw841HsTT+6MwZsqExbQvukgvnuS
|
||||
lA3Rg3xTFxodMaVZWsVQJy2PXGHVFRLnCp05DYZsMGZabuN9mIekYwtjePo89Lz0
|
||||
JtU3ibL3squGG3gg6TLtPjks7Txm18BYPOYLznui32GUz+1aIZuk2p5A/rSldsh3
|
||||
bke68IX5WZhKuIxT0+BjS8yfLWI0HCUs71WVxzvlJ1v22/eMK0WEA6+ZhCbOKIav
|
||||
VtGNJrwIYwhZmxqfiR1HzHTLvrV0SLlJ2bwNk/yzKm8IJfuFezQ5BBtQ2RS9opFX
|
||||
X8ft3v+uQQQvi+MCAwEAAaOBwzCBwDASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud
|
||||
DgQWBBTDrT6m1hEARYBcOldKim3cMQ2ecTAOBgNVHQ8BAf8EBAMCAQYwNAYDVR0f
|
||||
BC0wKzApoCegJYYjaHR0cDovL2NybC5pY2Fubi5vcmcvdG1jaF9waWxvdC5jcmww
|
||||
RQYDVR0gBD4wPDA6BgMqAwQwMzAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5pY2Fu
|
||||
bi5vcmcvcGlsb3RfcmVwb3NpdG9yeTANBgkqhkiG9w0BAQsFAAOCAQEAKUfEJ5X6
|
||||
QAttajjRVseJFQxRXGHTgCaDk8C/1nj1ielZAuZtgdUpWDUr0NnGCi+LHSsgdTYR
|
||||
+vMrxir7EVYQevrBobELkxeTEfjF9FVqjBHInyPFLOFkz15zGG2IwPJps+vhAd/7
|
||||
gT0ph1k2FEkJFGL5LwRf1ms4IX0vDkxTIX8Qxy1jczCiSsoV8pwlhh2NHAkpGQWN
|
||||
/pTS0Uqi7uU5Bm/IoGvPBzUp5n5SjUMnTZx/+1zAuerSabt483sXBcWsjgl7MqFt
|
||||
fONiAtNeMNfh60lTMu4zgVwLZTO4TQM5Q2uylPPmZtwnA88QvM2IL85cIYJHd0z9
|
||||
jpUQMBGHXF2WQA==
|
||||
-----END CERTIFICATE-----
|
13
java/google/registry/tmch/icann-tmch.crl
Normal file
13
java/google/registry/tmch/icann-tmch.crl
Normal file
|
@ -0,0 +1,13 @@
|
|||
-----BEGIN X509 CRL-----
|
||||
MIIB8DCB2QIBATANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJVUzE8MDoGA1UE
|
||||
ChMzSW50ZXJuZXQgQ29ycG9yYXRpb24gZm9yIEFzc2lnbmVkIE5hbWVzIGFuZCBO
|
||||
dW1iZXJzMSkwJwYDVQQDEyBJQ0FOTiBUcmFkZW1hcmsgQ2xlYXJpbmdob3VzZSBD
|
||||
QRcNMTMwNzI0MDAwMDAwWhcNMTUwNzIzMjM1OTU5WqAvMC0wHwYDVR0jBBgwFoAU
|
||||
XMDxlizKTFsp8UB00xs2PkfUbgQwCgYDVR0UBAMCAQEwDQYJKoZIhvcNAQELBQAD
|
||||
ggEBAGmBog7fIBEvnDCV1Pkaiosibnxpp8d3E9NQRJRBlyLSNJKTB7mwcRlyVl/q
|
||||
nAJrPZy6d01v58KpvrLLYRHkbsHC+qWom61Tu8ssVfGTrN6Re/CY8WtPQiiGjtDF
|
||||
ZQBLgmjRGsUZ8r5L9DjBQNHEQmEMXYMiV7mId2+08GBHT1QDgUw74RXMqsD8BzeF
|
||||
BqSyNJwxUzuCeU7xK4xRErnursXZo1fqgEc/zfqm9AjJx2frFmc/aXYvvw13z+ZP
|
||||
PPsPAeMDUO/LBCGZJAf28LUKn5NmVcUXhYkxprjLuLu7eMveRn7LAiK6LDKL4SXL
|
||||
AD6ouYPCx82fLnJ8o+2r6tHUHSk=
|
||||
-----END X509 CRL-----
|
25
java/google/registry/tmch/icann-tmch.crt
Normal file
25
java/google/registry/tmch/icann-tmch.crt
Normal file
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEQjCCAyqgAwIBAgIhAJNCMqhNjz3cXVJPj7yvcZvro1FKQR+dTC6tXazem5g+
|
||||
MA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNVBAYTAlVTMTwwOgYDVQQKEzNJbnRlcm5l
|
||||
dCBDb3Jwb3JhdGlvbiBmb3IgQXNzaWduZWQgTmFtZXMgYW5kIE51bWJlcnMxKTAn
|
||||
BgNVBAMTIElDQU5OIFRyYWRlbWFyayBDbGVhcmluZ2hvdXNlIENBMB4XDTEzMDcy
|
||||
NDAwMDAwMFoXDTIzMDcyMzIzNTk1OVowdjELMAkGA1UEBhMCVVMxPDA6BgNVBAoT
|
||||
M0ludGVybmV0IENvcnBvcmF0aW9uIGZvciBBc3NpZ25lZCBOYW1lcyBhbmQgTnVt
|
||||
YmVyczEpMCcGA1UEAxMgSUNBTk4gVHJhZGVtYXJrIENsZWFyaW5naG91c2UgQ0Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5MX6qpRnqFzEXa9w3G0b8
|
||||
LTEVZzpOpcSq2BXJO16+iuZ964mpay2hm2BdZk89hSmZhUy2ePBR6PdS0GMmzzXL
|
||||
NiyTHJlDIPxxXTR39Iqs8QChJ8wle4pYUu8JUk2vJ0r7PhFweeCCQZ5gvHdCwopS
|
||||
bXeolj4NCqsvzU8iROsLRHSZbE83i2pkL+qBoyzjny9MO2rvMNPo5WrDNrno6hvC
|
||||
hlf8Pv77HTNCazI2MeW0ArfLin4pSe6nLnDsQA11SF9bbgwDgVMQFvmB8nEvUbZW
|
||||
Atnp3auaWqaylC+G0p3frFvMCUJMPrghiPwBABl3bk1GLjXXVl7D8SubKd2Xwv63
|
||||
AgMBAAGjgbowgbcwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUXMDxlizK
|
||||
TFsp8UB00xs2PkfUbgQwDgYDVR0PAQH/BAQDAgEGMC4GA1UdHwQnMCUwI6AhoB+G
|
||||
HWh0dHA6Ly9jcmwuaWNhbm4ub3JnL3RtY2guY3JsMEIGA1UdIAQ7MDkwBgYEVR0g
|
||||
ADAvBggrBgEFBQcCATAjMCEGCCsGAQUFBwIBFhVodHRwczovL2NhLmljYW5uLm9y
|
||||
Zy8wDQYJKoZIhvcNAQELBQADggEBAAM29FBdwQSAx8dD4ZYtCYjXxTonNCP2qveG
|
||||
wrpMJcq/I3Jp/N4etsnj+K5ej5sSlDuo8sTMF7lgMkgjrc6zgJl0+Gct2RhbRNzN
|
||||
5ittE9JwJZ3Us4vwiy6gqMO5Ie9YaKMZy2MYP2iFp6AhBKIc2Iz+8aFfnFzdSEx2
|
||||
b3xc+t1A09dzpnzU6zvHWUUkTYq9fTg1er1npni9ZErvp0jEyHVWi5GXvWap68XH
|
||||
pVF6TBmPW2UBEEnFgd3SxbMXLhaD/wzV12tlSYjxaNed+H5qbVVbSVwN9yBeWU29
|
||||
/pkZu79TqFFxTd4CJTWOBq0+yLO1Ts2ZZ1l+GgU6e3hI1XERSNc=
|
||||
-----END CERTIFICATE-----
|
31
java/google/registry/tmch/marksdb-public-key.asc
Normal file
31
java/google/registry/tmch/marksdb-public-key.asc
Normal file
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v2.0.14 (GNU/Linux)
|
||||
|
||||
mQENBFHaxrYBCADHDPUdSYCs9wuBUhbGYfi39LKlDLhlmia7Tlv1rSJb8RPcU7DL
|
||||
eXSU9zKRDpW31KQtHnjARinobjQNc6YXQOGtjDVr3LGTojGT1y+obVmgWytpHNnb
|
||||
kSu1pktQRDPpYPBMBOD/MVpz8nZxgjYWyYTIkfTgkA/l1YKRyA30ieHVR8qKhJZY
|
||||
D6u/dViQcdZCLsvs0XYYhPDjNi7M2wzVAdRjh9txnJgDymgqHF59onzkZauyFnc9
|
||||
EUEUURITzbfMifvGSZ/h+zdCUT/tSH3wSuVRxXqAh+cApT2c+gI88/TlGci2YOZV
|
||||
POU24M7xuYPb9rIK4b9tR12ZQF+g/4n9eDprABEBAAG0LHN1cHBvcnQgYXQgbWFy
|
||||
a3NkYi5vcmcgPHN1cHBvcnRAbWFya3NkYi5vcmc+iQE4BBMBAgAiBQJR2sa2AhsD
|
||||
BgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRC4xOmbTP03TGTCB/9J1xKyNkJe
|
||||
wsSHhBga4MnBiS3gr3waZSfYaiWvx7br5FxI3Zv2jPAouqkpf8XbJgc4RvqZjFmi
|
||||
5YSwpLgChSa5DkA6kPgReNCEfPvtBX7iRrZTv7RSSzZUuGiVED9s4Qk0+ppc8RgW
|
||||
HRLYetvNvMIKL+6bvUlvztr+KM914JpzDEXgkBok2RxVq33FKUMIiy4vinbS2/7N
|
||||
LpGlCMG7vgdBHIjBItUq9ipCtiDvvmPHiwHErgMgkx67pmDGxViMHDIMpzb8YTHw
|
||||
lCxRwnLPILmm371hqOuVsu/pUVC16TEzYRDwZGbn4uKzWT0ZvYsPNcFGKC35ujEH
|
||||
3fACoP6CMt/AuQENBFHaxrYBCADuDjCyxpDMakGSmk5zXIW/r0F9wqpIaPqm7xoR
|
||||
15ajbvBUguM3gVH7bC4qihaSVFq2S8soWpyu88JyeWFiK+nMiIUALbfwXTA3DdIm
|
||||
zmA/aKBNCeMyWXH1iy5zDCO0S343HH3/QWYTTy0aSUoMiW4AovKrnbZWti0WOoC4
|
||||
a8MUY1Ib1DhG/CJBzx4l52m5jOnmKB4foa7VmaWAjlG/s4ZDoEUPW8p6d2fmF05q
|
||||
/ImGuzSwXegCuTY7rmkWskf4SshTYryyel76v1SteOFhXFaxcewbQgxIn1uGczd5
|
||||
P3/RBznF+Zm9OQK3840sqnhn1y/A3HadT7OI9Ot31TVXpRK5ABEBAAGJAR8EGAEC
|
||||
AAkFAlHaxrYCGwwACgkQuMTpm0z9N0yzzwgApO1IzPwd5OwFymQNIizKOl3VTBTR
|
||||
s5xtGBARg3fQsPmQen/SWZXu7VqJzr9QngQRJUT0nqdQPsGqOU72f8UkZGg6UD/o
|
||||
mqfSfzLnOGnXLpC5g4bR+Z1PHlOV33r0HnWA8GPZsnYOXw414NYLmNW+tlC9t1F0
|
||||
WNBevhzXQujBfj0p+Znf3Gap8xvtZ9EAbeYUEpUQXEKh5UcBHBQCzmvDgukISK7w
|
||||
wBpVWDwLHIYghE9OElat0y+ttD+RhSkUf+ufIw+L6BTHVD5E+W+BFoMFmNU3Kk/y
|
||||
BoGsCR/i63T3egBVn1IzFezNy8YUVTtqOLyYlIe2R35zafxXs2anwvqA6Q==
|
||||
=2ED8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
16
java/google/registry/tmch/package-info.java
Normal file
16
java/google/registry/tmch/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.tmch;
|
Loading…
Add table
Add a link
Reference in a new issue