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:
sarahcaseybot 2020-10-23 15:46:57 -04:00 committed by GitHub
parent f52e887db5
commit 576c05ff5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 441 additions and 105 deletions

View file

@ -40,7 +40,6 @@ import com.google.common.net.MediaType;
import google.registry.model.registrar.Registrar;
import google.registry.util.CertificateChecker;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
@ -389,9 +388,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_clientCertFileFlagWithViolation() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=blobio",
@ -419,9 +418,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_clientCertFileFlagWithMultipleViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=blobio",
@ -499,9 +498,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_failoverClientCertFileFlagWithViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=blobio",
@ -529,9 +528,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_failoverClientCertFileFlagWithMultipleViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=blobio",
@ -1181,52 +1180,10 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
"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
void testFailure_certHashAndCertFile() {
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=blobio",

View file

@ -41,7 +41,6 @@ import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension;
import google.registry.util.CertificateChecker;
import google.registry.util.CidrAddressBlock;
import java.security.cert.CertificateException;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
@ -267,9 +266,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getClientCertificate()).isNull();
assertThat(registrar.getClientCertificateHash()).isNull();
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())
.isEqualTo(
@ -284,9 +283,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getClientCertificate()).isNull();
assertThat(registrar.getClientCertificateHash()).isNull();
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())
.isEqualTo(
@ -300,9 +299,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getFailoverClientCertificate()).isNull();
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())
@ -317,9 +316,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getFailoverClientCertificate()).isNull();
CertificateException thrown =
IllegalArgumentException thrown =
assertThrows(
CertificateException.class,
IllegalArgumentException.class,
() ->
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())

View file

@ -43,6 +43,7 @@ import google.registry.util.EmailMessage;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.joda.time.DateTime;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.junit.jupiter.api.Test;
@ -367,6 +368,18 @@ class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase {
@Test
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(
Role.OWNER,
Registrar::getClientCertificate,
@ -374,15 +387,216 @@ class RegistrarSettingsActionTest extends RegistrarSettingsActionTestCase {
(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
void testUpdate_failoverClientCertificate() {
clock.setTo(DateTime.parse("2020-11-02T00:00:00Z"));
doTestUpdate(
Role.OWNER,
Registrar::getFailoverClientCertificate,
CertificateSamples.SAMPLE_CERT,
CertificateSamples.SAMPLE_CERT3,
(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
void testUpdate_allowedTlds() {
doTestUpdate(

View file

@ -24,6 +24,7 @@ import static google.registry.security.JsonHttpTestUtils.createJsonPayload;
import static google.registry.testing.DatastoreHelper.createTlds;
import static google.registry.testing.DatastoreHelper.disallowRegistrarAccess;
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.when;
@ -31,6 +32,7 @@ import com.google.appengine.api.users.User;
import com.google.common.collect.ImmutableList;
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.model.ofy.Ofy;
import google.registry.model.registrar.RegistrarContact;
@ -46,6 +48,7 @@ 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;
@ -115,6 +118,12 @@ public abstract class RegistrarSettingsActionTestCase {
AuthResult.create(
AuthLevel.USER,
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);
when(req.getMethod()).thenReturn("POST");
when(rsp.getWriter()).thenReturn(new PrintWriter(writer));

View file

@ -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_CERT2;
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.DatastoreHelper.loadRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
@ -27,6 +29,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registrar.Registrar;
import java.util.Map;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/**
@ -38,10 +41,11 @@ class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
@Test
void testPost_updateCert_success() throws Exception {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Registrar modified =
loadRegistrar(CLIENT_ID)
.asBuilder()
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
.setClientCertificate(SAMPLE_CERT3, clock.nowUtc())
.build();
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update",
@ -67,17 +71,58 @@ class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
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
void testChangeCertificates() throws Exception {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Map<String, Object> jsonMap = loadRegistrar(CLIENT_ID).toJsonMap();
jsonMap.put("clientCertificate", SAMPLE_CERT);
jsonMap.put("clientCertificate", SAMPLE_CERT3);
jsonMap.put("failoverClientCertificate", null);
Map<String, Object> response = action.handleJsonRequest(ImmutableMap.of(
"op", "update", "id", CLIENT_ID, "args", jsonMap));
assertThat(response).containsEntry("status", "SUCCESS");
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(registrar.getClientCertificate()).isEqualTo(SAMPLE_CERT);
assertThat(registrar.getClientCertificateHash()).isEqualTo(SAMPLE_CERT_HASH);
assertThat(registrar.getClientCertificate()).isEqualTo(SAMPLE_CERT3);
assertThat(registrar.getClientCertificateHash()).isEqualTo(SAMPLE_CERT3_HASH);
assertThat(registrar.getFailoverClientCertificate()).isNull();
assertThat(registrar.getFailoverClientCertificateHash()).isNull();
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
@ -86,14 +131,15 @@ class SecuritySettingsTest extends RegistrarSettingsActionTestCase {
@Test
void testChangeFailoverCertificate() throws Exception {
clock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
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(
"op", "update", "id", CLIENT_ID, "args", jsonMap));
assertThat(response).containsEntry("status", "SUCCESS");
Registrar registrar = loadRegistrar(CLIENT_ID);
assertThat(registrar.getFailoverClientCertificate()).isEqualTo(SAMPLE_CERT2);
assertThat(registrar.getFailoverClientCertificateHash()).isEqualTo(SAMPLE_CERT2_HASH);
assertThat(registrar.getFailoverClientCertificate()).isEqualTo(SAMPLE_CERT3);
assertThat(registrar.getFailoverClientCertificateHash()).isEqualTo(SAMPLE_CERT3_HASH);
assertMetric(CLIENT_ID, "update", "[OWNER]", "SUCCESS");
verifyNotificationEmailsSent();
}