Move CertificateChecker to core/ (#852)

* Move CertificateChecker to core/

* rename certificates/ to certs/
This commit is contained in:
sarahcaseybot 2020-10-30 15:57:12 -04:00 committed by GitHub
parent ef688796d0
commit e1eedb2e0a
10 changed files with 26 additions and 113 deletions

View file

@ -1,44 +0,0 @@
// Copyright 2020 The Nomulus 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 google.registry.config;
import com.google.common.collect.ImmutableSortedMap;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.CertificateChecker;
import google.registry.util.Clock;
import javax.inject.Singleton;
import org.joda.time.DateTime;
/** Dagger module that provides the {@link CertificateChecker} used in the application. */
// TODO(sarahbot@): Move this module to a better location. Possibly flows/. If we decide to move
// CertificateChecker.java to core/ delete this file and inject the CertificateChecker constructor
// instead.
@Module
public abstract class CertificateCheckerModule {
@Provides
@Singleton
static CertificateChecker provideCertificateChecker(
@Config("maxValidityDaysSchedule") ImmutableSortedMap<DateTime, Integer> validityDaysMap,
@Config("expirationWarningDays") int daysToExpiration,
@Config("minimumRsaKeyLength") int minimumRsaKeyLength,
Clock clock) {
return new CertificateChecker(validityDaysMap, daysToExpiration, minimumRsaKeyLength, clock);
}
private CertificateCheckerModule() {}
}

View file

@ -0,0 +1,221 @@
// Copyright 2020 The Nomulus 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 google.registry.flows.certs;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.Clock;
import google.registry.util.DateTimeUtils;
import java.io.ByteArrayInputStream;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Days;
/** An utility to check that a given certificate meets our requirements */
public class CertificateChecker {
private final ImmutableSortedMap<DateTime, Integer> maxValidityLengthSchedule;
private final int daysToExpiration;
private final int minimumRsaKeyLength;
private final Clock clock;
/**
* Constructs a CertificateChecker instance with the specified configuration parameters.
*
* <p>The max validity length schedule is a sorted map of {@link DateTime} to {@link Integer}
* entries representing a maximum validity period for certificates issued on or after that date.
* The first entry must have a key of {@link DateTimeUtils#START_OF_TIME}, such that every
* possible date has an applicable max validity period. Since security requirements tighten over
* time, the max validity periods will be decreasing as the date increases.
*
* <p>The validity length schedule used by all major Web browsers as of 2020Q4 would be
* represented as:
*
* <pre>
* ImmutableSortedMap.of(
* START_OF_TIME, 825,
* DateTime.parse("2020-09-01T00:00:00Z"), 398
* );
* </pre>
*/
@Inject
public CertificateChecker(
@Config("maxValidityDaysSchedule")
ImmutableSortedMap<DateTime, Integer> maxValidityLengthSchedule,
@Config("expirationWarningDays") int daysToExpiration,
@Config("minimumRsaKeyLength") int minimumRsaKeyLength,
Clock clock) {
checkArgument(
maxValidityLengthSchedule.containsKey(START_OF_TIME),
"Max validity length schedule must contain an entry for START_OF_TIME");
this.maxValidityLengthSchedule = maxValidityLengthSchedule;
this.daysToExpiration = daysToExpiration;
this.minimumRsaKeyLength = minimumRsaKeyLength;
this.clock = clock;
}
/**
* Checks the given certificate string for violations and throws an exception if any violations
* exist.
*/
public void validateCertificate(String certificateString) {
ImmutableSet<CertificateViolation> violations = checkCertificate(certificateString);
if (!violations.isEmpty()) {
String displayMessages =
violations.stream()
.map(violation -> getViolationDisplayMessage(violation))
.collect(Collectors.joining("\n"));
throw new IllegalArgumentException(displayMessages);
}
}
/**
* Checks a given certificate for violations and returns a list of all the violations the
* certificate has.
*/
public ImmutableSet<CertificateViolation> checkCertificate(X509Certificate certificate) {
ImmutableSet.Builder<CertificateViolation> violations = new ImmutableSet.Builder<>();
// Check if currently in validity period
Date now = clock.nowUtc().toDate();
if (certificate.getNotAfter().before(now)) {
violations.add(CertificateViolation.EXPIRED);
} else if (certificate.getNotBefore().after(now)) {
violations.add(CertificateViolation.NOT_YET_VALID);
}
// Check validity period length
int maxValidityDays =
maxValidityLengthSchedule.floorEntry(new DateTime(certificate.getNotBefore())).getValue();
if (getValidityLengthInDays(certificate) > maxValidityDays) {
violations.add(CertificateViolation.VALIDITY_LENGTH_TOO_LONG);
}
// Check key strengths
PublicKey key = certificate.getPublicKey();
if (key.getAlgorithm().equals("RSA")) {
RSAPublicKey rsaPublicKey = (RSAPublicKey) key;
if (rsaPublicKey.getModulus().bitLength() < minimumRsaKeyLength) {
violations.add(CertificateViolation.RSA_KEY_LENGTH_TOO_SHORT);
}
} else if (key.getAlgorithm().equals("EC")) {
// TODO(sarahbot): Add verification of ECDSA curves
} else {
violations.add(CertificateViolation.ALGORITHM_CONSTRAINED);
}
return violations.build();
}
/**
* Converts a given string to a certificate and checks it for violations, returning a list of all
* the violations the certificate has.
*/
public ImmutableSet<CertificateViolation> checkCertificate(String certificateString) {
X509Certificate certificate;
try {
certificate =
(X509Certificate)
CertificateFactory.getInstance("X509")
.generateCertificate(new ByteArrayInputStream(certificateString.getBytes(UTF_8)));
} catch (CertificateException e) {
throw new IllegalArgumentException("Unable to read given certificate.");
}
return checkCertificate(certificate);
}
/**
* Returns whether the certificate is nearing expiration.
*
* <p>Note that this is <i>all</i> that it checks. The certificate itself may well be expired or
* not yet valid and this message will still return false. So you definitely want to pair a call
* to this method with a call to {@link #checkCertificate} to determine other issues with the
* certificate that may be occurring.
*/
public boolean isNearingExpiration(X509Certificate certificate) {
Date nearingExpirationDate =
DateTime.parse(certificate.getNotAfter().toInstant().toString())
.minusDays(daysToExpiration)
.toDate();
return clock.nowUtc().toDate().after(nearingExpirationDate);
}
private static int getValidityLengthInDays(X509Certificate certificate) {
DateTime start = DateTime.parse(certificate.getNotBefore().toInstant().toString());
DateTime end = DateTime.parse(certificate.getNotAfter().toInstant().toString());
return Days.daysBetween(start.withTimeAtStartOfDay(), end.withTimeAtStartOfDay()).getDays();
}
private String getViolationDisplayMessage(CertificateViolation certificateViolation) {
// Yes, we'd rather do this as an instance method on the CertificateViolation enum itself, but
// we can't because we need access to configuration (injected as instance variables) which you
// can't get in a static enum context.
switch (certificateViolation) {
case EXPIRED:
return "Certificate is expired.";
case NOT_YET_VALID:
return "Certificate start date is in the future.";
case ALGORITHM_CONSTRAINED:
return "Certificate key algorithm must be RSA or ECDSA.";
case RSA_KEY_LENGTH_TOO_SHORT:
return String.format(
"RSA key length is too short; the minimum allowed length is %d bits.",
this.minimumRsaKeyLength);
case VALIDITY_LENGTH_TOO_LONG:
return String.format(
"Certificate validity period is too long; it must be less than or equal to %d days.",
this.maxValidityLengthSchedule.lastEntry().getValue());
default:
throw new IllegalArgumentException(
String.format(
"Unknown CertificateViolation enum value: %s", certificateViolation.name()));
}
}
/**
* The type of violation a certificate has based on the certificate requirements
* (go/registry-proxy-security).
*/
public enum CertificateViolation {
EXPIRED,
NOT_YET_VALID,
VALIDITY_LENGTH_TOO_LONG,
RSA_KEY_LENGTH_TOO_SHORT,
ALGORITHM_CONSTRAINED;
/**
* Gets a suitable end-user-facing display message for this particular certificate violation.
*
* <p>Note that the {@link CertificateChecker} instance must be passed in because it contains
* configuration values (e.g. minimum RSA key length) that go into the error message text.
*/
public String getDisplayMessage(CertificateChecker certificateChecker) {
return certificateChecker.getViolationDisplayMessage(this);
}
}
}

View file

@ -17,7 +17,6 @@ package google.registry.module.frontend;
import com.google.monitoring.metrics.MetricReporter;
import dagger.Component;
import dagger.Lazy;
import google.registry.config.CertificateCheckerModule;
import google.registry.config.CredentialModule;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.flows.ServerTridProviderModule;
@ -45,7 +44,6 @@ import javax.inject.Singleton;
@Component(
modules = {
AuthModule.class,
CertificateCheckerModule.class,
ConfigModule.class,
ConsoleConfigModule.class,
CredentialModule.class,

View file

@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registry.Registry;
@ -38,7 +39,6 @@ import google.registry.tools.params.OptionalLongParameter;
import google.registry.tools.params.OptionalPhoneNumberParameter;
import google.registry.tools.params.OptionalStringParameter;
import google.registry.tools.params.PathParameter;
import google.registry.util.CertificateChecker;
import google.registry.util.CidrAddressBlock;
import java.nio.file.Files;
import java.nio.file.Path;

View file

@ -20,7 +20,6 @@ import dagger.Lazy;
import google.registry.batch.BatchModule;
import google.registry.beam.initsql.BeamJpaModule;
import google.registry.bigquery.BigqueryModule;
import google.registry.config.CertificateCheckerModule;
import google.registry.config.CredentialModule.LocalCredentialJson;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule;
@ -61,7 +60,6 @@ import javax.inject.Singleton;
BatchModule.class,
BeamJpaModule.class,
BigqueryModule.class,
CertificateCheckerModule.class,
ConfigModule.class,
CloudDnsWriterModule.class,
DatastoreAdminModule.class,

View file

@ -38,6 +38,7 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryEnvironment;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarContact.Type;
@ -56,7 +57,6 @@ import google.registry.ui.forms.FormFieldException;
import google.registry.ui.server.RegistrarFormFields;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.AppEngineServiceUtils;
import google.registry.util.CertificateChecker;
import google.registry.util.CollectionUtils;
import google.registry.util.DiffUtils;
import java.util.HashSet;

View file

@ -0,0 +1,250 @@
// Copyright 2020 The Nomulus 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 google.registry.flows.certs;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.flows.certs.CertificateChecker.CertificateViolation.ALGORITHM_CONSTRAINED;
import static google.registry.flows.certs.CertificateChecker.CertificateViolation.EXPIRED;
import static google.registry.flows.certs.CertificateChecker.CertificateViolation.NOT_YET_VALID;
import static google.registry.flows.certs.CertificateChecker.CertificateViolation.RSA_KEY_LENGTH_TOO_SHORT;
import static google.registry.flows.certs.CertificateChecker.CertificateViolation.VALIDITY_LENGTH_TOO_LONG;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.testing.FakeClock;
import google.registry.util.SelfSignedCaCertificate;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link CertificateChecker} */
class CertificateCheckerTest {
private static final String SSL_HOST = "www.example.tld";
private FakeClock fakeClock = new FakeClock();
private CertificateChecker certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
2048,
fakeClock);
@Test
void test_checkCertificate_compliantCertPasses() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate)).isEmpty();
}
@Test
void test_checkCertificate_severalViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2010-01-01T00:00:00Z"));
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
keyGen.initialize(1024, new SecureRandom());
X509Certificate certificate =
SelfSignedCaCertificate.create(
keyGen.generateKeyPair(),
SSL_HOST,
DateTime.parse("2010-04-01T00:00:00Z"),
DateTime.parse("2014-07-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate))
.containsExactly(NOT_YET_VALID, VALIDITY_LENGTH_TOO_LONG, RSA_KEY_LENGTH_TOO_SHORT);
}
@Test
void test_checkCertificate_expiredCertificate() throws Exception {
fakeClock.setTo(DateTime.parse("2014-01-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2010-04-01T00:00:00Z"),
DateTime.parse("2012-07-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate)).containsExactly(EXPIRED);
}
@Test
void test_checkCertificate_notYetValid() throws Exception {
fakeClock.setTo(DateTime.parse("2010-01-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2010-04-01T00:00:00Z"),
DateTime.parse("2012-07-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate)).containsExactly(NOT_YET_VALID);
}
@Test
void test_checkCertificate_validityLengthWayTooLong() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2016-04-01T00:00:00Z"),
DateTime.parse("2021-07-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate))
.containsExactly(VALIDITY_LENGTH_TOO_LONG);
}
@Test
void test_checkCertificate_olderValidityLengthIssuedAfterCutoff() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2022-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate))
.containsExactly(VALIDITY_LENGTH_TOO_LONG);
}
@Test
void test_checkCertificate_olderValidityLengthIssuedBeforeCutoff() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2019-09-01T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate)).isEmpty();
}
@Test
void test_checkCertificate_rsaKeyLengthTooShort() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
keyGen.initialize(1024, new SecureRandom());
X509Certificate certificate =
SelfSignedCaCertificate.create(
keyGen.generateKeyPair(),
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate))
.containsExactly(RSA_KEY_LENGTH_TOO_SHORT);
}
@Test
void test_checkCertificate_rsaKeyLengthLongerThanRequired() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
keyGen.initialize(4096, new SecureRandom());
X509Certificate certificate =
SelfSignedCaCertificate.create(
keyGen.generateKeyPair(),
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate)).isEmpty();
}
@Test
void test_checkCertificate_invalidAlgorithm() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider());
keyGen.initialize(2048, new SecureRandom());
X509Certificate certificate =
SelfSignedCaCertificate.create(
keyGen.generateKeyPair(),
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.checkCertificate(certificate))
.containsExactly(ALGORITHM_CONSTRAINED);
}
@Test
void test_checkCertificate_validCertificateString() throws Exception {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
assertThat(certificateChecker.checkCertificate(SAMPLE_CERT3)).isEmpty();
assertThat(certificateChecker.checkCertificate(SAMPLE_CERT))
.containsExactly(VALIDITY_LENGTH_TOO_LONG);
}
@Test
void test_checkCertificate_invalidCertificateString() throws Exception {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> certificateChecker.checkCertificate("bad certificate string"));
assertThat(thrown).hasMessageThat().isEqualTo("Unable to read given certificate.");
}
@Test
void test_isNearingExpiration_yesItIs() throws Exception {
fakeClock.setTo(DateTime.parse("2021-09-20T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.isNearingExpiration(certificate)).isTrue();
}
@Test
void test_isNearingExpiration_noItsNot() throws Exception {
fakeClock.setTo(DateTime.parse("2021-07-20T00:00:00Z"));
X509Certificate certificate =
SelfSignedCaCertificate.create(
SSL_HOST,
DateTime.parse("2020-09-02T00:00:00Z"),
DateTime.parse("2021-10-01T00:00:00Z"))
.cert();
assertThat(certificateChecker.isNearingExpiration(certificate)).isFalse();
}
@Test
void test_CertificateViolation_RsaKeyLengthDisplayMessageFormatsCorrectly() {
assertThat(RSA_KEY_LENGTH_TOO_SHORT.getDisplayMessage(certificateChecker))
.isEqualTo("RSA key length is too short; the minimum allowed length is 2048 bits.");
}
@Test
void test_CertificateViolation_validityLengthDisplayMessageFormatsCorrectly() {
assertThat(VALIDITY_LENGTH_TOO_LONG.getDisplayMessage(certificateChecker))
.isEqualTo(
"Certificate validity period is too long; it must be less than or equal to 398 days.");
}
}

View file

@ -37,8 +37,8 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Range;
import com.google.common.net.MediaType;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.util.CertificateChecker;
import java.io.IOException;
import java.util.Optional;
import org.joda.money.CurrencyUnit;

View file

@ -34,12 +34,12 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension;
import google.registry.util.CertificateChecker;
import google.registry.util.CidrAddressBlock;
import java.util.Optional;
import org.joda.money.CurrencyUnit;

View file

@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.truth.Truth;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.ofy.Ofy;
import google.registry.model.registrar.RegistrarContact;
import google.registry.request.JsonActionRunner;
@ -48,7 +49,6 @@ import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import google.registry.ui.server.SendEmailUtils;
import google.registry.util.AppEngineServiceUtils;
import google.registry.util.CertificateChecker;
import google.registry.util.EmailMessage;
import google.registry.util.SendEmailService;
import java.io.PrintWriter;