mirror of
https://github.com/google/nomulus.git
synced 2025-07-01 16:53:35 +02:00
Add certificate checks to RegistrarSettingsAction (#843)
* Add certificate checks to RegistrarSettingsAction * Add some comments * Add more functionality to CertificateChecker and update call sites * Small code cleanups * Small format fix
This commit is contained in:
parent
f52e887db5
commit
576c05ff5f
10 changed files with 441 additions and 105 deletions
|
@ -17,6 +17,7 @@ package google.registry.module.frontend;
|
||||||
import com.google.monitoring.metrics.MetricReporter;
|
import com.google.monitoring.metrics.MetricReporter;
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import google.registry.config.CertificateCheckerModule;
|
||||||
import google.registry.config.CredentialModule;
|
import google.registry.config.CredentialModule;
|
||||||
import google.registry.config.RegistryConfig.ConfigModule;
|
import google.registry.config.RegistryConfig.ConfigModule;
|
||||||
import google.registry.flows.ServerTridProviderModule;
|
import google.registry.flows.ServerTridProviderModule;
|
||||||
|
@ -44,6 +45,7 @@ import javax.inject.Singleton;
|
||||||
@Component(
|
@Component(
|
||||||
modules = {
|
modules = {
|
||||||
AuthModule.class,
|
AuthModule.class,
|
||||||
|
CertificateCheckerModule.class,
|
||||||
ConfigModule.class,
|
ConfigModule.class,
|
||||||
ConsoleConfigModule.class,
|
ConsoleConfigModule.class,
|
||||||
CredentialModule.class,
|
CredentialModule.class,
|
||||||
|
|
|
@ -22,7 +22,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||||
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||||
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
|
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
|
||||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
import static org.joda.time.DateTimeZone.UTC;
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
|
@ -40,14 +39,9 @@ import google.registry.tools.params.OptionalPhoneNumberParameter;
|
||||||
import google.registry.tools.params.OptionalStringParameter;
|
import google.registry.tools.params.OptionalStringParameter;
|
||||||
import google.registry.tools.params.PathParameter;
|
import google.registry.tools.params.PathParameter;
|
||||||
import google.registry.util.CertificateChecker;
|
import google.registry.util.CertificateChecker;
|
||||||
import google.registry.util.CertificateChecker.CertificateViolation;
|
|
||||||
import google.registry.util.CidrAddressBlock;
|
import google.registry.util.CidrAddressBlock;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -55,7 +49,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
|
@ -369,7 +362,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
||||||
// existing certificate without providing a replacement. An uploaded empty certificate file
|
// existing certificate without providing a replacement. An uploaded empty certificate file
|
||||||
// will prevent the registrar from being able to establish EPP connections.
|
// will prevent the registrar from being able to establish EPP connections.
|
||||||
if (!asciiCert.equals("")) {
|
if (!asciiCert.equals("")) {
|
||||||
verifyCertificate(asciiCert);
|
certificateChecker.validateCertificate(asciiCert);
|
||||||
}
|
}
|
||||||
builder.setClientCertificate(asciiCert, now);
|
builder.setClientCertificate(asciiCert, now);
|
||||||
}
|
}
|
||||||
|
@ -378,7 +371,7 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
||||||
String asciiCert =
|
String asciiCert =
|
||||||
new String(Files.readAllBytes(failoverClientCertificateFilename), US_ASCII);
|
new String(Files.readAllBytes(failoverClientCertificateFilename), US_ASCII);
|
||||||
if (!asciiCert.equals("")) {
|
if (!asciiCert.equals("")) {
|
||||||
verifyCertificate(asciiCert);
|
certificateChecker.validateCertificate(asciiCert);
|
||||||
}
|
}
|
||||||
builder.setFailoverClientCertificate(asciiCert, now);
|
builder.setFailoverClientCertificate(asciiCert, now);
|
||||||
}
|
}
|
||||||
|
@ -482,22 +475,6 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyCertificate(String certificateString) throws CertificateException {
|
|
||||||
X509Certificate certificate =
|
|
||||||
(X509Certificate)
|
|
||||||
CertificateFactory.getInstance("X509")
|
|
||||||
.generateCertificate(new ByteArrayInputStream(certificateString.getBytes(UTF_8)));
|
|
||||||
ImmutableSet<CertificateViolation> violations =
|
|
||||||
certificateChecker.checkCertificate(certificate);
|
|
||||||
if (!violations.isEmpty()) {
|
|
||||||
String displayMessages =
|
|
||||||
violations.stream()
|
|
||||||
.map(violation -> violation.getDisplayMessage(certificateChecker))
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
throw new CertificateException(displayMessages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String execute() throws Exception {
|
protected String execute() throws Exception {
|
||||||
// Save registrar to Datastore and output its response
|
// Save registrar to Datastore and output its response
|
||||||
|
|
|
@ -56,6 +56,7 @@ import google.registry.ui.forms.FormFieldException;
|
||||||
import google.registry.ui.server.RegistrarFormFields;
|
import google.registry.ui.server.RegistrarFormFields;
|
||||||
import google.registry.ui.server.SendEmailUtils;
|
import google.registry.ui.server.SendEmailUtils;
|
||||||
import google.registry.util.AppEngineServiceUtils;
|
import google.registry.util.AppEngineServiceUtils;
|
||||||
|
import google.registry.util.CertificateChecker;
|
||||||
import google.registry.util.CollectionUtils;
|
import google.registry.util.CollectionUtils;
|
||||||
import google.registry.util.DiffUtils;
|
import google.registry.util.DiffUtils;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -92,6 +93,7 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
|
||||||
@Inject SendEmailUtils sendEmailUtils;
|
@Inject SendEmailUtils sendEmailUtils;
|
||||||
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
@Inject AuthenticatedRegistrarAccessor registrarAccessor;
|
||||||
@Inject AuthResult authResult;
|
@Inject AuthResult authResult;
|
||||||
|
@Inject CertificateChecker certificateChecker;
|
||||||
|
|
||||||
@Inject RegistrarSettingsAction() {}
|
@Inject RegistrarSettingsAction() {}
|
||||||
|
|
||||||
|
@ -306,19 +308,43 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
|
||||||
RegistrarFormFields.IP_ADDRESS_ALLOW_LIST_FIELD
|
RegistrarFormFields.IP_ADDRESS_ALLOW_LIST_FIELD
|
||||||
.extractUntyped(args)
|
.extractUntyped(args)
|
||||||
.orElse(ImmutableList.of()));
|
.orElse(ImmutableList.of()));
|
||||||
RegistrarFormFields.CLIENT_CERTIFICATE_FIELD
|
|
||||||
.extractUntyped(args)
|
Optional<String> certificateString =
|
||||||
.ifPresent(
|
RegistrarFormFields.CLIENT_CERTIFICATE_FIELD.extractUntyped(args);
|
||||||
certificate -> builder.setClientCertificate(certificate, tm().getTransactionTime()));
|
if (certificateString.isPresent()) {
|
||||||
RegistrarFormFields.FAILOVER_CLIENT_CERTIFICATE_FIELD
|
if (validateCertificate(initialRegistrar.getClientCertificate(), certificateString.get())) {
|
||||||
.extractUntyped(args)
|
builder.setClientCertificate(certificateString.get(), tm().getTransactionTime());
|
||||||
.ifPresent(
|
}
|
||||||
certificate ->
|
}
|
||||||
builder.setFailoverClientCertificate(certificate, tm().getTransactionTime()));
|
|
||||||
|
Optional<String> failoverCertificateString =
|
||||||
|
RegistrarFormFields.FAILOVER_CLIENT_CERTIFICATE_FIELD.extractUntyped(args);
|
||||||
|
if (failoverCertificateString.isPresent()) {
|
||||||
|
if (validateCertificate(
|
||||||
|
initialRegistrar.getFailoverClientCertificate(), failoverCertificateString.get())) {
|
||||||
|
builder.setFailoverClientCertificate(
|
||||||
|
failoverCertificateString.get(), tm().getTransactionTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return checkNotChangedUnlessAllowed(builder, initialRegistrar, Role.OWNER);
|
return checkNotChangedUnlessAllowed(builder, initialRegistrar, Role.OWNER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the registrar should accept the new certificate. Returns false if the
|
||||||
|
* certificate is already the one stored for the registrar.
|
||||||
|
*/
|
||||||
|
private boolean validateCertificate(String existingCertificate, String certificateString) {
|
||||||
|
if ((existingCertificate == null) || !existingCertificate.equals(certificateString)) {
|
||||||
|
// TODO(sarhabot): remove this check after November 1, 2020
|
||||||
|
if (tm().getTransactionTime().isAfter(DateTime.parse("2020-11-01T00:00:00Z"))) {
|
||||||
|
certificateChecker.validateCertificate(certificateString);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a registrar with the ADMIN-controlled args from the http request.
|
* Updates a registrar with the ADMIN-controlled args from the http request.
|
||||||
*
|
*
|
||||||
|
|
|
@ -40,7 +40,6 @@ import com.google.common.net.MediaType;
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.util.CertificateChecker;
|
import google.registry.util.CertificateChecker;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -389,9 +388,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||||
@Test
|
@Test
|
||||||
void testFail_clientCertFileFlagWithViolation() throws Exception {
|
void testFail_clientCertFileFlagWithViolation() throws Exception {
|
||||||
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--name=blobio",
|
"--name=blobio",
|
||||||
|
@ -419,9 +418,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||||
@Test
|
@Test
|
||||||
void testFail_clientCertFileFlagWithMultipleViolations() throws Exception {
|
void testFail_clientCertFileFlagWithMultipleViolations() throws Exception {
|
||||||
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--name=blobio",
|
"--name=blobio",
|
||||||
|
@ -499,9 +498,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||||
@Test
|
@Test
|
||||||
void testFail_failoverClientCertFileFlagWithViolations() throws Exception {
|
void testFail_failoverClientCertFileFlagWithViolations() throws Exception {
|
||||||
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--name=blobio",
|
"--name=blobio",
|
||||||
|
@ -529,9 +528,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||||
@Test
|
@Test
|
||||||
void testFail_failoverClientCertFileFlagWithMultipleViolations() throws Exception {
|
void testFail_failoverClientCertFileFlagWithMultipleViolations() throws Exception {
|
||||||
fakeClock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--name=blobio",
|
"--name=blobio",
|
||||||
|
@ -1181,52 +1180,10 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
|
||||||
"clientz"));
|
"clientz"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testFailure_invalidCertFileContents() {
|
|
||||||
assertThrows(
|
|
||||||
CertificateException.class,
|
|
||||||
() ->
|
|
||||||
runCommandForced(
|
|
||||||
"--name=blobio",
|
|
||||||
"--password=some_password",
|
|
||||||
"--registrar_type=REAL",
|
|
||||||
"--iana_id=8",
|
|
||||||
"--cert_file=" + writeToTmpFile("ABCDEF"),
|
|
||||||
"--passcode=01234",
|
|
||||||
"--icann_referral_email=foo@bar.test",
|
|
||||||
"--street=\"123 Fake St\"",
|
|
||||||
"--city Fakington",
|
|
||||||
"--state MA",
|
|
||||||
"--zip 00351",
|
|
||||||
"--cc US",
|
|
||||||
"clientz"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testFailure_invalidFailoverCertFileContents() {
|
|
||||||
assertThrows(
|
|
||||||
CertificateException.class,
|
|
||||||
() ->
|
|
||||||
runCommandForced(
|
|
||||||
"--name=blobio",
|
|
||||||
"--password=some_password",
|
|
||||||
"--registrar_type=REAL",
|
|
||||||
"--iana_id=8",
|
|
||||||
"--failover_cert_file=" + writeToTmpFile("ABCDEF"),
|
|
||||||
"--passcode=01234",
|
|
||||||
"--icann_referral_email=foo@bar.test",
|
|
||||||
"--street=\"123 Fake St\"",
|
|
||||||
"--city Fakington",
|
|
||||||
"--state MA",
|
|
||||||
"--zip 00351",
|
|
||||||
"--cc US",
|
|
||||||
"clientz"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_certHashAndCertFile() {
|
void testFailure_certHashAndCertFile() {
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--name=blobio",
|
"--name=blobio",
|
||||||
|
|
|
@ -41,7 +41,6 @@ import google.registry.persistence.VKey;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
import google.registry.util.CertificateChecker;
|
import google.registry.util.CertificateChecker;
|
||||||
import google.registry.util.CidrAddressBlock;
|
import google.registry.util.CidrAddressBlock;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -267,9 +266,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||||
assertThat(registrar.getClientCertificate()).isNull();
|
assertThat(registrar.getClientCertificate()).isNull();
|
||||||
assertThat(registrar.getClientCertificateHash()).isNull();
|
assertThat(registrar.getClientCertificateHash()).isNull();
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||||
assertThat(thrown.getMessage())
|
assertThat(thrown.getMessage())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
|
@ -284,9 +283,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||||
assertThat(registrar.getClientCertificate()).isNull();
|
assertThat(registrar.getClientCertificate()).isNull();
|
||||||
assertThat(registrar.getClientCertificateHash()).isNull();
|
assertThat(registrar.getClientCertificateHash()).isNull();
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||||
assertThat(thrown.getMessage())
|
assertThat(thrown.getMessage())
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
|
@ -300,9 +299,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||||
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||||
assertThat(registrar.getFailoverClientCertificate()).isNull();
|
assertThat(registrar.getFailoverClientCertificate()).isNull();
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||||
assertThat(thrown.getMessage())
|
assertThat(thrown.getMessage())
|
||||||
|
@ -317,9 +316,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
|
||||||
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
|
||||||
Registrar registrar = loadRegistrar("NewRegistrar");
|
Registrar registrar = loadRegistrar("NewRegistrar");
|
||||||
assertThat(registrar.getFailoverClientCertificate()).isNull();
|
assertThat(registrar.getFailoverClientCertificate()).isNull();
|
||||||
CertificateException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
CertificateException.class,
|
IllegalArgumentException.class,
|
||||||
() ->
|
() ->
|
||||||
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
|
||||||
assertThat(thrown.getMessage())
|
assertThat(thrown.getMessage())
|
||||||
|
|
|
@ -43,6 +43,7 @@ import google.registry.util.EmailMessage;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.json.simple.JSONValue;
|
import org.json.simple.JSONValue;
|
||||||
import org.json.simple.parser.ParseException;
|
import org.json.simple.parser.ParseException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -367,6 +368,18 @@ class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testUpdate_clientCertificate() {
|
void testUpdate_clientCertificate() {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
|
doTestUpdate(
|
||||||
|
Role.OWNER,
|
||||||
|
Registrar::getClientCertificate,
|
||||||
|
CertificateSamples.SAMPLE_CERT3,
|
||||||
|
(builder, s) -> builder.setClientCertificate(s, clock.nowUtc()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_clientCertificateWithViolationsBeforeNovemberSucceeds() {
|
||||||
|
// TODO(sarahbot): remove this test after November 1, 2020.
|
||||||
|
clock.setTo(DateTime.parse("2018-07-02T00:00:00Z"));
|
||||||
doTestUpdate(
|
doTestUpdate(
|
||||||
Role.OWNER,
|
Role.OWNER,
|
||||||
Registrar::getClientCertificate,
|
Registrar::getClientCertificate,
|
||||||
|
@ -374,15 +387,216 @@ class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase {
|
||||||
(builder, s) -> builder.setClientCertificate(s, clock.nowUtc()));
|
(builder, s) -> builder.setClientCertificate(s, clock.nowUtc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_otherFieldsWhenClientCertificateWithViolationsAlreadyExistedSucceeds() {
|
||||||
|
// TODO(sarahbot): remove this test after November 1, 2020.
|
||||||
|
|
||||||
|
// The frontend will always send the entire registrar entity back for an update, so the checks
|
||||||
|
// on the certificate should only run if a new certificate is being uploaded. All other updates
|
||||||
|
// after November 1st should still succeed even if a bad certificate is stored.
|
||||||
|
|
||||||
|
// Set a bad certificate before checks on uploads are enforced
|
||||||
|
clock.setTo(DateTime.parse("2018-07-02T00:00:00Z"));
|
||||||
|
Registrar existingRegistrar = loadRegistrar(CLIENT_ID);
|
||||||
|
existingRegistrar =
|
||||||
|
existingRegistrar
|
||||||
|
.asBuilder()
|
||||||
|
.setClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
|
||||||
|
.build();
|
||||||
|
persistResource(existingRegistrar);
|
||||||
|
|
||||||
|
// Update the other registrar fields after enforcement begins should succeed
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("url", "test.url");
|
||||||
|
args.put("phoneNumber", "+1.1234567890");
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response).containsEntry("status", "SUCCESS");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_clientCertificateWithViolationsAlreadyExistedSucceeds() {
|
||||||
|
// TODO(sarahbot): remove this test after November 1, 2020.
|
||||||
|
|
||||||
|
// The frontend will always send the entire registrar entity back for an update, so the checks
|
||||||
|
// on the certificate should only run if it is a new certificate
|
||||||
|
|
||||||
|
// Set a bad certificate before checks on uploads are enforced
|
||||||
|
clock.setTo(DateTime.parse("2018-07-02T00:00:00Z"));
|
||||||
|
Registrar existingRegistrar = loadRegistrar(CLIENT_ID);
|
||||||
|
existingRegistrar =
|
||||||
|
existingRegistrar
|
||||||
|
.asBuilder()
|
||||||
|
.setClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
|
||||||
|
.build();
|
||||||
|
persistResource(existingRegistrar);
|
||||||
|
|
||||||
|
// Update with the same certificate after enforcement starts
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("clientCertificate", CertificateSamples.SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response).containsEntry("status", "SUCCESS");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
||||||
|
assertNoTasksEnqueued("sheet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_clientCertificateWithViolationsFails() {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("clientCertificate", CertificateSamples.SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response)
|
||||||
|
.containsExactly(
|
||||||
|
"status",
|
||||||
|
"ERROR",
|
||||||
|
"results",
|
||||||
|
ImmutableList.of(),
|
||||||
|
"message",
|
||||||
|
"Certificate validity period is too long; it must be less than or equal to 398"
|
||||||
|
+ " days.");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
|
||||||
|
assertNoTasksEnqueued("sheet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_clientCertificateWithMultipleViolationsFails() {
|
||||||
|
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("clientCertificate", CertificateSamples.SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response)
|
||||||
|
.containsExactly(
|
||||||
|
"status",
|
||||||
|
"ERROR",
|
||||||
|
"results",
|
||||||
|
ImmutableList.of(),
|
||||||
|
"message",
|
||||||
|
"Certificate is expired.\nCertificate validity period is too long; it must be less"
|
||||||
|
+ " than or equal to 398 days.");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
|
||||||
|
assertNoTasksEnqueued("sheet");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testUpdate_failoverClientCertificate() {
|
void testUpdate_failoverClientCertificate() {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
doTestUpdate(
|
doTestUpdate(
|
||||||
Role.OWNER,
|
Role.OWNER,
|
||||||
Registrar::getFailoverClientCertificate,
|
Registrar::getFailoverClientCertificate,
|
||||||
CertificateSamples.SAMPLE_CERT,
|
CertificateSamples.SAMPLE_CERT3,
|
||||||
(builder, s) -> builder.setFailoverClientCertificate(s, clock.nowUtc()));
|
(builder, s) -> builder.setFailoverClientCertificate(s, clock.nowUtc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_failoverClientCertificateWithViolationsAlreadyExistedSucceeds() {
|
||||||
|
// TODO(sarahbot): remove this test after November 1, 2020.
|
||||||
|
|
||||||
|
// The frontend will always send the entire registrar entity back for an update, so the checks
|
||||||
|
// on the certificate should only run if it is a new certificate
|
||||||
|
|
||||||
|
// Set a bad certificate before checks on uploads are enforced
|
||||||
|
clock.setTo(DateTime.parse("2018-07-02T00:00:00Z"));
|
||||||
|
Registrar existingRegistrar = loadRegistrar(CLIENT_ID);
|
||||||
|
existingRegistrar =
|
||||||
|
existingRegistrar
|
||||||
|
.asBuilder()
|
||||||
|
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
|
||||||
|
.build();
|
||||||
|
persistResource(existingRegistrar);
|
||||||
|
|
||||||
|
// Update with the same certificate after enforcement starts
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("failoverClientCertificate", CertificateSamples.SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response).containsEntry("status", "SUCCESS");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
||||||
|
assertNoTasksEnqueued("sheet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_failoverClientCertificateWithViolationsFails() {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("failoverClientCertificate", CertificateSamples.SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response)
|
||||||
|
.containsExactly(
|
||||||
|
"status",
|
||||||
|
"ERROR",
|
||||||
|
"results",
|
||||||
|
ImmutableList.of(),
|
||||||
|
"message",
|
||||||
|
"Certificate validity period is too long; it must be less than or equal to 398"
|
||||||
|
+ " days.");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
|
||||||
|
assertNoTasksEnqueued("sheet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdate_failoverClientCertificateWithMultipleViolationsFails() {
|
||||||
|
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
||||||
|
Map<String, Object> args = Maps.newHashMap(loadRegistrar(CLIENT_ID).toJsonMap());
|
||||||
|
args.put("failoverClientCertificate", CertificateSamples.SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", args));
|
||||||
|
|
||||||
|
assertThat(response)
|
||||||
|
.containsExactly(
|
||||||
|
"status",
|
||||||
|
"ERROR",
|
||||||
|
"results",
|
||||||
|
ImmutableList.of(),
|
||||||
|
"message",
|
||||||
|
"Certificate is expired.\nCertificate validity period is too long; it must be less"
|
||||||
|
+ " than or equal to 398 days.");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
|
||||||
|
assertNoTasksEnqueued("sheet");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testUpdate_allowedTlds() {
|
void testUpdate_allowedTlds() {
|
||||||
doTestUpdate(
|
doTestUpdate(
|
||||||
|
|
|
@ -24,6 +24,7 @@ import static google.registry.security.JsonHttpTestUtils.createJsonPayload;
|
||||||
import static google.registry.testing.DatastoreHelper.createTlds;
|
import static google.registry.testing.DatastoreHelper.createTlds;
|
||||||
import static google.registry.testing.DatastoreHelper.disallowRegistrarAccess;
|
import static google.registry.testing.DatastoreHelper.disallowRegistrarAccess;
|
||||||
import static google.registry.testing.DatastoreHelper.loadRegistrar;
|
import static google.registry.testing.DatastoreHelper.loadRegistrar;
|
||||||
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ import com.google.appengine.api.users.User;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
|
import com.google.common.collect.ImmutableSortedMap;
|
||||||
import com.google.common.truth.Truth;
|
import com.google.common.truth.Truth;
|
||||||
import google.registry.model.ofy.Ofy;
|
import google.registry.model.ofy.Ofy;
|
||||||
import google.registry.model.registrar.RegistrarContact;
|
import google.registry.model.registrar.RegistrarContact;
|
||||||
|
@ -46,6 +48,7 @@ import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.InjectExtension;
|
import google.registry.testing.InjectExtension;
|
||||||
import google.registry.ui.server.SendEmailUtils;
|
import google.registry.ui.server.SendEmailUtils;
|
||||||
import google.registry.util.AppEngineServiceUtils;
|
import google.registry.util.AppEngineServiceUtils;
|
||||||
|
import google.registry.util.CertificateChecker;
|
||||||
import google.registry.util.EmailMessage;
|
import google.registry.util.EmailMessage;
|
||||||
import google.registry.util.SendEmailService;
|
import google.registry.util.SendEmailService;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
@ -115,6 +118,12 @@ public abstract class RegistrarSettingsActionTestCase {
|
||||||
AuthResult.create(
|
AuthResult.create(
|
||||||
AuthLevel.USER,
|
AuthLevel.USER,
|
||||||
UserAuthInfo.create(new User("user@email.com", "email.com", "12345"), false));
|
UserAuthInfo.create(new User("user@email.com", "email.com", "12345"), false));
|
||||||
|
action.certificateChecker =
|
||||||
|
new CertificateChecker(
|
||||||
|
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
|
||||||
|
30,
|
||||||
|
2048,
|
||||||
|
clock);
|
||||||
inject.setStaticField(Ofy.class, "clock", clock);
|
inject.setStaticField(Ofy.class, "clock", clock);
|
||||||
when(req.getMethod()).thenReturn("POST");
|
when(req.getMethod()).thenReturn("POST");
|
||||||
when(rsp.getWriter()).thenReturn(new PrintWriter(writer));
|
when(rsp.getWriter()).thenReturn(new PrintWriter(writer));
|
||||||
|
|
|
@ -18,6 +18,8 @@ import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
|
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
|
||||||
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2;
|
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2;
|
||||||
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2_HASH;
|
import static google.registry.testing.CertificateSamples.SAMPLE_CERT2_HASH;
|
||||||
|
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3;
|
||||||
|
import static google.registry.testing.CertificateSamples.SAMPLE_CERT3_HASH;
|
||||||
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
|
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
|
||||||
import static google.registry.testing.DatastoreHelper.loadRegistrar;
|
import static google.registry.testing.DatastoreHelper.loadRegistrar;
|
||||||
import static google.registry.testing.DatastoreHelper.persistResource;
|
import static google.registry.testing.DatastoreHelper.persistResource;
|
||||||
|
@ -27,6 +29,7 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,10 +41,11 @@ class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPost_updateCert_success() throws Exception {
|
void testPost_updateCert_success() throws Exception {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||||
Registrar modified =
|
Registrar modified =
|
||||||
loadRegistrar(CLIENT_ID)
|
loadRegistrar(CLIENT_ID)
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
|
.setClientCertificate(SAMPLE_CERT3, clock.nowUtc())
|
||||||
.build();
|
.build();
|
||||||
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
|
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
|
||||||
"op", "update",
|
"op", "update",
|
||||||
|
@ -67,17 +71,58 @@ class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
|
||||||
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: FormFieldException");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPost_updateCertWithViolations_failure() {
|
||||||
|
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
||||||
|
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
|
||||||
|
reqJson.put("clientCertificate", SAMPLE_CERT);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", reqJson));
|
||||||
|
assertThat(response).containsEntry("status", "ERROR");
|
||||||
|
assertThat(response)
|
||||||
|
.containsEntry(
|
||||||
|
"message",
|
||||||
|
"Certificate is expired.\nCertificate validity period is too long; it must be less"
|
||||||
|
+ " than or equal to 398 days.");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPost_updateFailoverCertWithViolations_failure() {
|
||||||
|
clock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
|
||||||
|
Map<String, Object> reqJson = loadRegistrar(CLIENT_ID).toJsonMap();
|
||||||
|
reqJson.put("failoverClientCertificate", SAMPLE_CERT2);
|
||||||
|
Map<String, Object> response =
|
||||||
|
action.handleJsonRequest(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"op", "update",
|
||||||
|
"id", CLIENT_ID,
|
||||||
|
"args", reqJson));
|
||||||
|
assertThat(response).containsEntry("status", "ERROR");
|
||||||
|
assertThat(response)
|
||||||
|
.containsEntry(
|
||||||
|
"message",
|
||||||
|
"Certificate is expired.\nCertificate validity period is too long; it must be less"
|
||||||
|
+ " than or equal to 398 days.");
|
||||||
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "ERROR: IllegalArgumentException");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testChangeCertificates() throws Exception {
|
void testChangeCertificates() throws Exception {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||||
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
|
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
|
||||||
jsonMap.put("clientCertificate", SAMPLE_CERT);
|
jsonMap.put("clientCertificate", SAMPLE_CERT3);
|
||||||
jsonMap.put("failoverClientCertificate", null);
|
jsonMap.put("failoverClientCertificate", null);
|
||||||
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
|
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
|
||||||
"op", "update", "id", CLIENT_ID, "args", jsonMap));
|
"op", "update", "id", CLIENT_ID, "args", jsonMap));
|
||||||
assertThat(response).containsEntry("status", "SUCCESS");
|
assertThat(response).containsEntry("status", "SUCCESS");
|
||||||
Registrar registrar = loadRegistrar(CLIENT_ID);
|
Registrar registrar = loadRegistrar(CLIENT_ID);
|
||||||
assertThat(registrar.getClientCertificate()).isEqualTo(SAMPLE_CERT);
|
assertThat(registrar.getClientCertificate()).isEqualTo(SAMPLE_CERT3);
|
||||||
assertThat(registrar.getClientCertificateHash()).isEqualTo(SAMPLE_CERT_HASH);
|
assertThat(registrar.getClientCertificateHash()).isEqualTo(SAMPLE_CERT3_HASH);
|
||||||
assertThat(registrar.getFailoverClientCertificate()).isNull();
|
assertThat(registrar.getFailoverClientCertificate()).isNull();
|
||||||
assertThat(registrar.getFailoverClientCertificateHash()).isNull();
|
assertThat(registrar.getFailoverClientCertificateHash()).isNull();
|
||||||
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
||||||
|
@ -86,14 +131,15 @@ class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testChangeFailoverCertificate() throws Exception {
|
void testChangeFailoverCertificate() throws Exception {
|
||||||
|
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||||
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
|
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
|
||||||
jsonMap.put("failoverClientCertificate", SAMPLE_CERT2);
|
jsonMap.put("failoverClientCertificate", SAMPLE_CERT3);
|
||||||
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
|
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
|
||||||
"op", "update", "id", CLIENT_ID, "args", jsonMap));
|
"op", "update", "id", CLIENT_ID, "args", jsonMap));
|
||||||
assertThat(response).containsEntry("status", "SUCCESS");
|
assertThat(response).containsEntry("status", "SUCCESS");
|
||||||
Registrar registrar = loadRegistrar(CLIENT_ID);
|
Registrar registrar = loadRegistrar(CLIENT_ID);
|
||||||
assertThat(registrar.getFailoverClientCertificate()).isEqualTo(SAMPLE_CERT2);
|
assertThat(registrar.getFailoverClientCertificate()).isEqualTo(SAMPLE_CERT3);
|
||||||
assertThat(registrar.getFailoverClientCertificateHash()).isEqualTo(SAMPLE_CERT2_HASH);
|
assertThat(registrar.getFailoverClientCertificateHash()).isEqualTo(SAMPLE_CERT3_HASH);
|
||||||
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
|
||||||
verifyNotificationEmailsSent();
|
verifyNotificationEmailsSent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,18 @@ package google.registry.util;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
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.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableSortedMap;
|
import com.google.common.collect.ImmutableSortedMap;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Days;
|
import org.joda.time.Days;
|
||||||
|
|
||||||
|
@ -68,8 +73,23 @@ public class CertificateChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks a certificate for violations and returns a list of all the violations the certificate
|
* Checks the given certificate string for violations and throws an exception if any violations
|
||||||
* has.
|
* 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) {
|
public ImmutableSet<CertificateViolation> checkCertificate(X509Certificate certificate) {
|
||||||
ImmutableSet.Builder<CertificateViolation> violations = new ImmutableSet.Builder<>();
|
ImmutableSet.Builder<CertificateViolation> violations = new ImmutableSet.Builder<>();
|
||||||
|
@ -104,6 +124,25 @@ public class CertificateChecker {
|
||||||
return violations.build();
|
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.
|
* Returns whether the certificate is nearing expiration.
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,6 +21,7 @@ import static google.registry.util.CertificateChecker.CertificateViolation.NOT_Y
|
||||||
import static google.registry.util.CertificateChecker.CertificateViolation.RSA_KEY_LENGTH_TOO_SHORT;
|
import static google.registry.util.CertificateChecker.CertificateViolation.RSA_KEY_LENGTH_TOO_SHORT;
|
||||||
import static google.registry.util.CertificateChecker.CertificateViolation.VALIDITY_LENGTH_TOO_LONG;
|
import static google.registry.util.CertificateChecker.CertificateViolation.VALIDITY_LENGTH_TOO_LONG;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSortedMap;
|
import com.google.common.collect.ImmutableSortedMap;
|
||||||
import google.registry.testing.FakeClock;
|
import google.registry.testing.FakeClock;
|
||||||
|
@ -35,6 +36,54 @@ import org.junit.jupiter.api.Test;
|
||||||
class CertificateCheckerTest {
|
class CertificateCheckerTest {
|
||||||
|
|
||||||
private static final String SSL_HOST = "www.example.tld";
|
private static final String SSL_HOST = "www.example.tld";
|
||||||
|
private static final String GOOD_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIIDyzCCArOgAwIBAgIUJnhiVrxAxgwkLJzHPm1w/lBoNs4wDQYJKoZIhvcNAQEL\n"
|
||||||
|
+ "BQAwdTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhO\n"
|
||||||
|
+ "ZXcgWW9yazEPMA0GA1UECgwGR29vZ2xlMR0wGwYDVQQLDBRkb21haW4tcmVnaXN0\n"
|
||||||
|
+ "cnktdGVzdDEQMA4GA1UEAwwHY2xpZW50MTAeFw0yMDEwMTIxNzU5NDFaFw0yMTA0\n"
|
||||||
|
+ "MzAxNzU5NDFaMHUxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8G\n"
|
||||||
|
+ "A1UEBwwITmV3IFlvcmsxDzANBgNVBAoMBkdvb2dsZTEdMBsGA1UECwwUZG9tYWlu\n"
|
||||||
|
+ "LXJlZ2lzdHJ5LXRlc3QxEDAOBgNVBAMMB2NsaWVudDEwggEiMA0GCSqGSIb3DQEB\n"
|
||||||
|
+ "AQUAA4IBDwAwggEKAoIBAQC0msirO7kXyGEC93stsNYGc02Z77Q2qfHFwaGYkUG8\n"
|
||||||
|
+ "QvOF5SWN+jwTo5Td6Jj26A26a8MLCtK45TCBuMRNcUsHhajhT19ocphO20iY3zhi\n"
|
||||||
|
+ "ycwV1id0iwME4kPd1m57BELRE9tUPOxF81/JQXdR1fwT5KRVHYRDWZhaZ5aBmlZY\n"
|
||||||
|
+ "3t/H9Ly0RBYyApkMaGs3nlb94OOug6SouUfRt02S59ja3wsE2SVF/Eui647OXP7O\n"
|
||||||
|
+ "QdYXofxuqLoNkE8EnAdl43/enGLiCIVd0G2lABibFF+gbxTtfgbg7YtfUZJdL+Mb\n"
|
||||||
|
+ "RAcAtuLXEamNQ9H63JgVF16PlQVCDz2XyI3uCfPpDDiBAgMBAAGjUzBRMB0GA1Ud\n"
|
||||||
|
+ "DgQWBBQ26bWk8qfEBjXs/xZ4m8JZyalnITAfBgNVHSMEGDAWgBQ26bWk8qfEBjXs\n"
|
||||||
|
+ "/xZ4m8JZyalnITAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAZ\n"
|
||||||
|
+ "VcsgslBKanKOieJ5ik2d9qzOMXKfBuWPRFWbkC3t9i5awhHqnGAaj6nICnnMZIyt\n"
|
||||||
|
+ "rdx5lZW5aaQyf0EP/90JAA8Xmty4A6MXmEjQAMiCOpP3A7eeS6Xglgi8IOZl4/bg\n"
|
||||||
|
+ "LonW62TUkilo5IiFt/QklFTeHIjXB+OvA8+2Quqyd+zp7v6KnhXjvaomim78DhwE\n"
|
||||||
|
+ "0PIUnjmiRpGpHfTVioTdfhPHZ2Y93Y8K7juL93sQog9aBu5m9XRJCY6wGyWPE83i\n"
|
||||||
|
+ "kmLfGzjcnaJ6kqCd9xQRFZ0JwHmGlkAQvFoeengbNUqSyjyVgsOoNkEsrWwe/JFO\n"
|
||||||
|
+ "iqBvjEhJlvRoefvkdR98\n"
|
||||||
|
+ "-----END CERTIFICATE-----\n";
|
||||||
|
private static final String BAD_CERTIFICATE =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
+ "MIIDvTCCAqWgAwIBAgIJANoEy6mYwalPMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV\n"
|
||||||
|
+ "BAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxDzAN\n"
|
||||||
|
+ "BgNVBAoMBkdvb2dsZTEdMBsGA1UECwwUZG9tYWluLXJlZ2lzdHJ5LXRlc3QxEDAO\n"
|
||||||
|
+ "BgNVBAMMB2NsaWVudDIwHhcNMTUwODI2MTkyODU3WhcNNDMwMTExMTkyODU3WjB1\n"
|
||||||
|
+ "MQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZ\n"
|
||||||
|
+ "b3JrMQ8wDQYDVQQKDAZHb29nbGUxHTAbBgNVBAsMFGRvbWFpbi1yZWdpc3RyeS10\n"
|
||||||
|
+ "ZXN0MRAwDgYDVQQDDAdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
|
||||||
|
+ "CgKCAQEAw2FtuDyoR+rUJHp6k7KwaoHGHPV1xnC8IpG9O0SZubOXrFrnBHggBsbu\n"
|
||||||
|
+ "+DsknbHXjmoihSFFem0KQqJg5y34aDAHXQV3iqa7nDfb1x4oc5voVz9gqjdmGKNm\n"
|
||||||
|
+ "WF4MTIPNMu8KY52M852mMCxODK+6MZYp7wCmVa63KdCm0bW/XsLgoA/+FVGwKLhf\n"
|
||||||
|
+ "UqFzt10Cf+87zl4VHrSaJqcHBYM6yAO5lvkr5VC6g8rRQ+dJ+pBT2D99YpSF1aFc\n"
|
||||||
|
+ "rWbBreIypixZAnXm/Xoogu6RnohS29VCJp2dXFAJmKXGwyKNQFXfEKxZBaBi8uKH\n"
|
||||||
|
+ "XF459795eyF9xHgSckEgu7jZlxOk6wIDAQABo1AwTjAdBgNVHQ4EFgQUv26AsQyc\n"
|
||||||
|
+ "kLOjkhqcFLOuueB33l4wHwYDVR0jBBgwFoAUv26AsQyckLOjkhqcFLOuueB33l4w\n"
|
||||||
|
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEANBuV+QDISSnGAEHKbR40\n"
|
||||||
|
+ "zUYdOjdZ399zcFNqTSPHwmE0Qu8pbmXhofpBfjzrcv0tkVbhSLYnT22qhx7aDmhb\n"
|
||||||
|
+ "bOS8CeVYCwl5eiDTkJly3pRZLzJpy+UT5z8SPxO3MrTqn+wuj0lBpWRTBCWYAUpr\n"
|
||||||
|
+ "IFRmgVB3IwVb60UIuxhmuk8TVss2SzNrdhdt36eAIPJ0RWEb0KHYHi35Y6lt4f+t\n"
|
||||||
|
+ "iVk+ZR0cCbHUs7Q1RqREXHd/ICuMRLY/MsadVQ9WDqVOridh198X/OIqdx/p9kvJ\n"
|
||||||
|
+ "1R80jDcVGNhYVXLmHu4ho4xrOaliSYvUJSCmaaSEGVZ/xE5PI7S6A8RMdj0iXLSt\n"
|
||||||
|
+ "Bg==\n"
|
||||||
|
+ "-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
private FakeClock fakeClock = new FakeClock();
|
private FakeClock fakeClock = new FakeClock();
|
||||||
private CertificateChecker certificateChecker =
|
private CertificateChecker certificateChecker =
|
||||||
|
@ -189,6 +238,24 @@ class CertificateCheckerTest {
|
||||||
.containsExactly(ALGORITHM_CONSTRAINED);
|
.containsExactly(ALGORITHM_CONSTRAINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test_checkCertificate_validCertificateString() throws Exception {
|
||||||
|
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
|
||||||
|
assertThat(certificateChecker.checkCertificate(GOOD_CERTIFICATE)).isEmpty();
|
||||||
|
assertThat(certificateChecker.checkCertificate(BAD_CERTIFICATE))
|
||||||
|
.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
|
@Test
|
||||||
void test_isNearingExpiration_yesItIs() throws Exception {
|
void test_isNearingExpiration_yesItIs() throws Exception {
|
||||||
fakeClock.setTo(DateTime.parse("2021-09-20T00:00:00Z"));
|
fakeClock.setTo(DateTime.parse("2021-09-20T00:00:00Z"));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue