Use CertificateChecker on login (#936)

* Use CertificateChecker on login

* Add actual enforcement of requirements in sandbox

* Add new Exceptions

* add validation command to RegistryToolComponent

* Fix error messages

* Add a test for production behavior

* check logs in test

* move loghandler
This commit is contained in:
sarahcaseybot 2021-01-22 16:32:15 -05:00 committed by GitHub
parent a75640ec9c
commit a7c3a4e3b0
12 changed files with 315 additions and 62 deletions

View file

@ -25,7 +25,10 @@ import com.google.common.net.InetAddresses;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.flows.EppException.AuthenticationErrorException;
import google.registry.flows.certs.CertificateChecker;
import google.registry.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.registrar.Registrar;
import google.registry.request.Header;
import google.registry.util.CidrAddressBlock;
@ -60,17 +63,20 @@ public class TlsCredentials implements TransportCredentials {
private final Optional<String> clientCertificateHash;
private final Optional<String> clientCertificate;
private final Optional<InetAddress> clientInetAddr;
private final CertificateChecker certificateChecker;
@Inject
public TlsCredentials(
@Config("requireSslCertificates") boolean requireSslCertificates,
@Header("X-SSL-Certificate") Optional<String> clientCertificateHash,
@Header("X-SSL-Full-Certificate") Optional<String> clientCertificate,
@Header("X-Forwarded-For") Optional<String> clientAddress) {
@Header("X-Forwarded-For") Optional<String> clientAddress,
CertificateChecker certificateChecker) {
this.requireSslCertificates = requireSslCertificates;
this.clientCertificateHash = clientCertificateHash;
this.clientCertificate = clientCertificate;
this.clientInetAddr = clientAddress.map(TlsCredentials::parseInetAddress);
this.certificateChecker = certificateChecker;
}
static InetAddress parseInetAddress(String asciiAddr) {
@ -146,6 +152,24 @@ public class TlsCredentials implements TransportCredentials {
// Check if the certificate is equal to the one on file for the registrar.
if (clientCertificate.equals(registrar.getClientCertificate())
|| clientCertificate.equals(registrar.getFailoverClientCertificate())) {
// Check certificate for any requirement violations
// TODO(Sarahbot@): Throw exceptions instead of just logging once requirement enforcement
// begins
try {
certificateChecker.validateCertificate(clientCertificate.get());
} catch (InsecureCertificateException e) {
// throw exception in unit tests and Sandbox
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|| RegistryEnvironment.get().equals(RegistryEnvironment.SANDBOX)) {
throw new CertificateContainsSecurityViolationsException(e);
}
logger.atWarning().log(
"Registrar certificate used for %s does not meet certificate requirements: %s",
registrar.getClientId(), e.getMessage());
} catch (Exception e) {
logger.atWarning().log(
"Error validating certificate for %s: %s", registrar.getClientId(), e);
}
// successfully validated, return here since hash validation is not necessary
return;
}
@ -211,6 +235,20 @@ public class TlsCredentials implements TransportCredentials {
}
}
/** Registrar certificate contains the following security violations: ... */
public static class CertificateContainsSecurityViolationsException
extends AuthenticationErrorException {
InsecureCertificateException exception;
CertificateContainsSecurityViolationsException(InsecureCertificateException exception) {
super(
String.format(
"Registrar certificate contains the following security violations:\n%s",
exception.getMessage()));
this.exception = exception;
}
}
/** Registrar certificate not present. */
public static class MissingRegistrarCertificateException extends AuthenticationErrorException {
MissingRegistrarCertificateException() {

View file

@ -89,14 +89,14 @@ public class CertificateChecker {
* Checks the given certificate string for violations and throws an exception if any violations
* exist.
*/
public void validateCertificate(String certificateString) {
public void validateCertificate(String certificateString) throws InsecureCertificateException {
ImmutableSet<CertificateViolation> violations = checkCertificate(certificateString);
if (!violations.isEmpty()) {
String displayMessages =
violations.stream()
.map(violation -> getViolationDisplayMessage(violation))
.collect(Collectors.joining("\n"));
throw new IllegalArgumentException(displayMessages);
throw new InsecureCertificateException(violations, displayMessages);
}
}
@ -258,4 +258,14 @@ public class CertificateChecker {
return certificateChecker.getViolationDisplayMessage(this);
}
}
/** Exception to throw when a certificate has security violations. */
public static class InsecureCertificateException extends Exception {
ImmutableSet<CertificateViolation> violations;
InsecureCertificateException(ImmutableSet<CertificateViolation> violations, String message) {
super(message);
this.violations = violations;
}
}
}

View file

@ -168,6 +168,8 @@ interface RegistryToolComponent {
void inject(ValidateEscrowDepositCommand command);
void inject(ValidateLoginCredentialsCommand command);
void inject(WhoisQueryCommand command);
AppEngineConnection appEngineConnection();

View file

@ -25,12 +25,14 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.flows.TlsCredentials;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.tools.params.PathParameter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** A command to test registrar login credentials. */
@Parameters(separators = " =", commandDescription = "Test registrar login credentials")
@ -68,6 +70,8 @@ final class ValidateLoginCredentialsCommand implements CommandWithRemoteApi {
description = "Client ip address to pretend to use")
private String clientIpAddress = "10.0.0.1";
@Inject CertificateChecker certificateChecker;
@Override
public void run() throws Exception {
checkArgument(
@ -85,7 +89,8 @@ final class ValidateLoginCredentialsCommand implements CommandWithRemoteApi {
true,
Optional.ofNullable(clientCertificateHash),
Optional.ofNullable(clientCertificate),
Optional.ofNullable(clientIpAddress))
Optional.ofNullable(clientIpAddress),
certificateChecker)
.validate(registrar, password);
checkState(
registrar.isLive(), "Registrar %s has non-live state: %s", clientId, registrar.getState());

View file

@ -39,6 +39,7 @@ 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.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registrar.RegistrarContact.Type;
@ -337,7 +338,11 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
private boolean validateCertificate(
Optional<String> existingCertificate, String certificateString) {
if (!existingCertificate.isPresent() || !existingCertificate.get().equals(certificateString)) {
try {
certificateChecker.validateCertificate(certificateString);
} catch (InsecureCertificateException e) {
throw new IllegalArgumentException(e.getMessage());
}
return true;
}
return false;

View file

@ -16,16 +16,33 @@ package google.registry.flows;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.testing.TestLogHandler;
import google.registry.config.RegistryEnvironment;
import google.registry.flows.certs.CertificateChecker;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.CertificateSamples;
import google.registry.testing.SystemPropertyExtension;
import google.registry.util.SelfSignedCaCertificate;
import java.io.StringWriter;
import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.testcontainers.shaded.org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.testcontainers.shaded.org.bouncycastle.util.io.pem.PemWriter;
/** Test logging in with TLS credentials. */
class EppLoginTlsTest extends EppTestCase {
@ -34,13 +51,30 @@ class EppLoginTlsTest extends EppTestCase {
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
@RegisterExtension
@Order(value = Integer.MAX_VALUE)
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
private final CertificateChecker certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
private final Logger loggerToIntercept =
Logger.getLogger(TlsCredentials.class.getCanonicalName());
private final TestLogHandler handler = new TestLogHandler();
void setCredentials(String clientCertificateHash, String clientCertificate) {
setTransportCredentials(
new TlsCredentials(
true,
Optional.ofNullable(clientCertificateHash),
Optional.ofNullable(clientCertificate),
Optional.of("192.168.1.100:54321")));
Optional.of("192.168.1.100:54321"),
certificateChecker));
}
@BeforeEach
@ -48,7 +82,7 @@ class EppLoginTlsTest extends EppTestCase {
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, DateTime.now(UTC))
.setClientCertificate(CertificateSamples.SAMPLE_CERT3, DateTime.now(UTC))
.build());
// Set a cert for the second registrar, or else any cert will be allowed for login.
persistResource(
@ -56,18 +90,19 @@ class EppLoginTlsTest extends EppTestCase {
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT2, DateTime.now(UTC))
.build());
loggerToIntercept.addHandler(handler);
}
@Test
void testLoginLogout() throws Exception {
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
assertThatLogoutSucceeds();
}
@Test
void testLogin_wrongPasswordFails() throws Exception {
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
// For TLS login, we also check the epp xml password.
assertThatLogin("NewRegistrar", "incorrect")
.hasResponse(
@ -77,7 +112,7 @@ class EppLoginTlsTest extends EppTestCase {
@Test
void testMultiLogin() throws Exception {
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
assertThatLogoutSucceeds();
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
@ -120,12 +155,12 @@ class EppLoginTlsTest extends EppTestCase {
@Test
void testGoodPrimaryCertificate() throws Exception {
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
DateTime now = DateTime.now(UTC);
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, now)
.setClientCertificate(CertificateSamples.SAMPLE_CERT3, now)
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
.build());
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
@ -133,26 +168,26 @@ class EppLoginTlsTest extends EppTestCase {
@Test
void testGoodFailoverCertificate() throws Exception {
setCredentials(CertificateSamples.SAMPLE_CERT2_HASH, CertificateSamples.SAMPLE_CERT2);
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
DateTime now = DateTime.now(UTC);
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, now)
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT3, now)
.build());
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
}
@Test
void testMissingPrimaryCertificateButHasFailover_usesFailover() throws Exception {
setCredentials(CertificateSamples.SAMPLE_CERT2_HASH, CertificateSamples.SAMPLE_CERT2);
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
DateTime now = DateTime.now(UTC);
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(null, now)
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT3, now)
.build());
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
}
@ -172,4 +207,85 @@ class EppLoginTlsTest extends EppTestCase {
"response_error.xml",
ImmutableMap.of("CODE", "2200", "MSG", "Registrar certificate is not configured"));
}
@Test
void testCertificateDoesNotMeetRequirements_fails() throws Exception {
// SAMPLE_CERT has a validity period that is too long
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, clock.nowUtc())
.build());
assertThatLogin("NewRegistrar", "foo-BAR2")
.hasResponse(
"response_error.xml",
ImmutableMap.of(
"CODE",
"2200",
"MSG",
"Registrar certificate contains the following security violations:\n"
+ "Certificate validity period is too long; it must be less than or equal to"
+ " 398 days."));
}
@Test
void testCertificateDoesNotMeetMultipleRequirements_fails() throws Exception {
X509Certificate certificate =
SelfSignedCaCertificate.create(
"test", clock.nowUtc().plusDays(100), clock.nowUtc().plusDays(5000))
.cert();
StringWriter sw = new StringWriter();
try (PemWriter pw = new PemWriter(sw)) {
PemObjectGenerator generator = new JcaMiscPEMGenerator(certificate);
pw.writeObject(generator);
}
// SAMPLE_CERT has a validity period that is too long
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, sw.toString());
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(sw.toString(), clock.nowUtc())
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, clock.nowUtc())
.build());
assertThatLogin("NewRegistrar", "foo-BAR2")
.hasResponse(
"response_error.xml",
ImmutableMap.of(
"CODE",
"2200",
"MSG",
"Registrar certificate contains the following security violations:\n"
+ "Certificate is expired.\n"
+ "Certificate validity period is too long; it must be less than or equal to"
+ " 398 days."));
}
@Test
// TODO(sarahbot@): Remove this test once requirements are enforced in production
void testCertificateDoesNotMeetRequirementsInProduction_succeeds() throws Exception {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
// SAMPLE_CERT has a validity period that is too long
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, clock.nowUtc())
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, clock.nowUtc())
.build());
// Even though the certificate contains security violations, the login will succeed in
// production
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
assertAboutLogs()
.that(handler)
.hasLogAtLevelWithMessage(
Level.WARNING,
"Registrar certificate used for NewRegistrar does not meet certificate requirements:"
+ " Certificate validity period is too long; it must be less than or equal to 398"
+ " days.");
}
}

View file

@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.testing.TestDataHelper.loadFile;
import static google.registry.testing.TestLogHandlerUtils.findFirstLogMessageByPrefix;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@ -27,8 +28,10 @@ import static org.mockito.Mockito.verify;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.flogger.LoggerConfig;
import com.google.common.testing.TestLogHandler;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.eppcommon.Trid;
import google.registry.model.eppoutput.EppOutput.ResponseOrGreeting;
import google.registry.model.eppoutput.EppResponse;
@ -38,6 +41,7 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeHttpSession;
import java.util.List;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@ -54,6 +58,16 @@ class FlowRunnerTest {
private final TestLogHandler handler = new TestLogHandler();
protected final FakeClock clock = new FakeClock();
private final CertificateChecker certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
static class TestCommandFlow implements Flow {
@Override
public ResponseOrGreeting run() {
@ -139,7 +153,11 @@ class FlowRunnerTest {
void testRun_loggingStatement_tlsCredentials() throws Exception {
flowRunner.credentials =
new TlsCredentials(
true, Optional.of("abc123def"), Optional.of("cert"), Optional.of("127.0.0.1"));
true,
Optional.of("abc123def"),
Optional.of("cert"),
Optional.of("127.0.0.1"),
certificateChecker);
flowRunner.run(eppMetricBuilder);
assertThat(Splitter.on("\n\t").split(findFirstLogMessageByPrefix(handler, "EPP Command\n\t")))
.contains("TlsCredentials{clientCertificateHash=abc123def, clientAddress=/127.0.0.1}");

View file

@ -18,17 +18,20 @@ import static com.google.common.truth.Truth8.assertThat;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.time.DateTimeZone.UTC;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
import google.registry.flows.TlsCredentials.RegistrarCertificateNotConfiguredException;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeClock;
import google.registry.util.CidrAddressBlock;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
@ -43,6 +46,16 @@ final class TlsCredentialsTest {
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
protected final FakeClock clock = new FakeClock();
private final CertificateChecker certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@Test
void testProvideClientCertificateHash() {
HttpServletRequest req = mock(HttpServletRequest.class);
@ -53,11 +66,16 @@ final class TlsCredentialsTest {
@Test
void testClientCertificateAndHash_missing() {
TlsCredentials tls =
new TlsCredentials(true, Optional.empty(), Optional.empty(), Optional.of("192.168.1.1"));
new TlsCredentials(
true,
Optional.empty(),
Optional.empty(),
Optional.of("192.168.1.1"),
certificateChecker);
persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setClientCertificate(SAMPLE_CERT, DateTime.now(UTC))
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
.build());
assertThrows(
MissingRegistrarCertificateException.class,
@ -67,11 +85,12 @@ final class TlsCredentialsTest {
@Test
void test_missingIpAddress_doesntAllowAccess() {
TlsCredentials tls =
new TlsCredentials(false, Optional.of("certHash"), Optional.empty(), Optional.empty());
new TlsCredentials(
false, Optional.of("certHash"), Optional.empty(), Optional.empty(), certificateChecker);
persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setClientCertificate(SAMPLE_CERT, DateTime.now(UTC))
.setClientCertificate(SAMPLE_CERT, clock.nowUtc())
.setIpAddressAllowList(ImmutableSet.of(CidrAddressBlock.create("3.5.8.13")))
.build());
assertThrows(
@ -83,12 +102,16 @@ final class TlsCredentialsTest {
void test_validateCertificate_canBeConfiguredToBypassCertHashes() throws Exception {
TlsCredentials tls =
new TlsCredentials(
false, Optional.of("certHash"), Optional.of("cert"), Optional.of("192.168.1.1"));
false,
Optional.of("certHash"),
Optional.of("cert"),
Optional.of("192.168.1.1"),
certificateChecker);
persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setClientCertificate(null, DateTime.now(UTC))
.setFailoverClientCertificate(null, DateTime.now(UTC))
.setClientCertificate(null, clock.nowUtc())
.setFailoverClientCertificate(null, clock.nowUtc())
.build());
// This would throw a RegistrarCertificateNotConfiguredException if cert hashes were not
// bypassed
@ -105,7 +128,11 @@ final class TlsCredentialsTest {
void testClientCertificate_notConfigured() {
TlsCredentials tls =
new TlsCredentials(
true, Optional.of("hash"), Optional.of(SAMPLE_CERT), Optional.of("192.168.1.1"));
true,
Optional.of("hash"),
Optional.of(SAMPLE_CERT),
Optional.of("192.168.1.1"),
certificateChecker);
persistResource(loadRegistrar("TheRegistrar").asBuilder().build());
assertThrows(
RegistrarCertificateNotConfiguredException.class,
@ -116,12 +143,16 @@ final class TlsCredentialsTest {
void test_validateCertificate_canBeConfiguredToBypassCerts() throws Exception {
TlsCredentials tls =
new TlsCredentials(
false, Optional.of("certHash"), Optional.of("cert"), Optional.of("192.168.1.1"));
false,
Optional.of("certHash"),
Optional.of("cert"),
Optional.of("192.168.1.1"),
certificateChecker);
persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setClientCertificate(null, DateTime.now(UTC))
.setFailoverClientCertificate(null, DateTime.now(UTC))
.setClientCertificate(null, clock.nowUtc())
.setFailoverClientCertificate(null, clock.nowUtc())
.build());
// This would throw a RegistrarCertificateNotConfiguredException if cert hashes wren't bypassed.
tls.validateCertificate(Registrar.loadByClientId("TheRegistrar").get());

View file

@ -15,14 +15,18 @@
package google.registry.flows.session;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.net.InetAddresses;
import google.registry.flows.TlsCredentials;
import google.registry.flows.TlsCredentials.BadRegistrarCertificateException;
import google.registry.flows.TlsCredentials.BadRegistrarIpAddressException;
import google.registry.flows.TlsCredentials.MissingRegistrarCertificateException;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.testing.CertificateSamples;
import google.registry.util.CidrAddressBlock;
@ -33,9 +37,9 @@ import org.junit.jupiter.api.Test;
/** Unit tests for {@link LoginFlow} when accessed via a TLS transport. */
public class LoginFlowViaTlsTest extends LoginFlowTestCase {
private static final Optional<String> GOOD_CERT = Optional.of(CertificateSamples.SAMPLE_CERT);
private static final Optional<String> GOOD_CERT = Optional.of(CertificateSamples.SAMPLE_CERT3);
private static final Optional<String> GOOD_CERT_HASH =
Optional.of(CertificateSamples.SAMPLE_CERT_HASH);
Optional.of(CertificateSamples.SAMPLE_CERT3_HASH);
private static final Optional<String> BAD_CERT = Optional.of(CertificateSamples.SAMPLE_CERT2);
private static final Optional<String> BAD_CERT_HASH =
Optional.of(CertificateSamples.SAMPLE_CERT2_HASH);
@ -43,11 +47,18 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
private static final Optional<String> BAD_IP = Optional.of("1.1.1.1");
private static final Optional<String> GOOD_IPV6 = Optional.of("2001:db8::1");
private static final Optional<String> BAD_IPV6 = Optional.of("2001:db8::2");
private final CertificateChecker certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
clock);
@Override
protected Registrar.Builder getRegistrarBuilder() {
return super.getRegistrarBuilder()
.setClientCertificate(CertificateSamples.SAMPLE_CERT, DateTime.now(UTC))
.setClientCertificate(GOOD_CERT.get(), DateTime.now(UTC))
.setIpAddressAllowList(
ImmutableList.of(CidrAddressBlock.create(InetAddresses.forString(GOOD_IP.get()), 32)));
}
@ -55,7 +66,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
@Test
void testSuccess_withGoodCredentials() throws Exception {
persistResource(getRegistrarBuilder().build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IP);
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IP, certificateChecker);
doSuccessfulTest("login_valid.xml");
}
@ -66,7 +77,8 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
.setIpAddressAllowList(
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
.build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IPV6);
credentials =
new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IPV6, certificateChecker);
doSuccessfulTest("login_valid.xml");
}
@ -77,7 +89,8 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
.setIpAddressAllowList(
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
.build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IPV6);
credentials =
new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IPV6, certificateChecker);
doSuccessfulTest("login_valid.xml");
}
@ -87,14 +100,14 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
getRegistrarBuilder()
.setIpAddressAllowList(ImmutableList.of(CidrAddressBlock.create("192.168.1.255/24")))
.build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IP);
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IP, certificateChecker);
doSuccessfulTest("login_valid.xml");
}
@Test
void testFailure_incorrectClientCertificateHash() {
persistResource(getRegistrarBuilder().build());
credentials = new TlsCredentials(true, BAD_CERT_HASH, BAD_CERT, GOOD_IP);
credentials = new TlsCredentials(true, BAD_CERT_HASH, BAD_CERT, GOOD_IP, certificateChecker);
doFailingTest("login_valid.xml", BadRegistrarCertificateException.class);
}
@ -102,14 +115,16 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
// TODO(Sarahbot): This should fail once hash fallback is removed
void testSuccess_missingClientCertificate() throws Exception {
persistResource(getRegistrarBuilder().build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, Optional.empty(), GOOD_IP);
credentials =
new TlsCredentials(true, GOOD_CERT_HASH, Optional.empty(), GOOD_IP, certificateChecker);
doSuccessfulTest("login_valid.xml");
}
@Test
void testFailure_missingClientCertificateAndHash() {
persistResource(getRegistrarBuilder().build());
credentials = new TlsCredentials(true, Optional.empty(), Optional.empty(), GOOD_IP);
credentials =
new TlsCredentials(true, Optional.empty(), Optional.empty(), GOOD_IP, certificateChecker);
doFailingTest("login_valid.xml", MissingRegistrarCertificateException.class);
}
@ -122,7 +137,8 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
.build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, Optional.empty());
credentials =
new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, Optional.empty(), certificateChecker);
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
}
@ -135,7 +151,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
.build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, BAD_IP);
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, BAD_IP, certificateChecker);
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
}
@ -148,7 +164,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
CidrAddressBlock.create(InetAddresses.forString("192.168.1.1"), 32),
CidrAddressBlock.create(InetAddresses.forString("2001:db8::1"), 128)))
.build());
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, BAD_IPV6);
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, BAD_IPV6, certificateChecker);
doFailingTest("login_valid.xml", BadRegistrarIpAddressException.class);
}
}

View file

@ -38,6 +38,7 @@ 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.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.registrar.Registrar;
import java.io.IOException;
import java.util.Optional;
@ -389,9 +390,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_clientCertFileFlagWithViolation() throws Exception {
fakeClock.setTo(DateTime.parse("2020-10-01T00:00:00Z"));
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() ->
runCommandForced(
"--name=blobio",
@ -419,9 +420,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_clientCertFileFlagWithMultipleViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() ->
runCommandForced(
"--name=blobio",
@ -476,9 +477,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_failoverClientCertFileFlagWithViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() ->
runCommandForced(
"--name=blobio",
@ -506,9 +507,9 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
@Test
void testFail_failoverClientCertFileFlagWithMultipleViolations() throws Exception {
fakeClock.setTo(DateTime.parse("2055-11-01T00:00:00Z"));
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() ->
runCommandForced(
"--name=blobio",

View file

@ -33,6 +33,7 @@ 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.flows.certs.CertificateChecker.InsecureCertificateException;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.Registrar.Type;
@ -265,9 +266,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getClientCertificate()).isEmpty();
assertThat(registrar.getClientCertificateHash()).isEmpty();
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())
.isEqualTo(
@ -282,9 +283,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getClientCertificate()).isEmpty();
assertThat(registrar.getClientCertificateHash()).isEmpty();
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() -> runCommand("--cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())
.isEqualTo(
@ -298,9 +299,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() ->
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())
@ -315,9 +316,9 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
fakeClock.setTo(DateTime.parse("2055-10-01T00:00:00Z"));
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
IllegalArgumentException thrown =
InsecureCertificateException thrown =
assertThrows(
IllegalArgumentException.class,
InsecureCertificateException.class,
() ->
runCommand("--failover_cert_file=" + getCertFilename(), "--force", "NewRegistrar"));
assertThat(thrown.getMessage())

View file

@ -20,14 +20,17 @@ import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.flows.EppException;
import google.registry.flows.TransportCredentials.BadRegistrarPasswordException;
import google.registry.flows.certs.CertificateChecker;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.testing.CertificateSamples;
@ -42,7 +45,7 @@ import org.junit.jupiter.api.Test;
class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginCredentialsCommand> {
private static final String PASSWORD = "foo-BAR2";
private static final String CERT_HASH = CertificateSamples.SAMPLE_CERT_HASH;
private static final String CERT_HASH = CertificateSamples.SAMPLE_CERT3_HASH;
private static final String CLIENT_IP = "1.2.3.4";
@BeforeEach
@ -52,11 +55,18 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
loadRegistrar("NewRegistrar")
.asBuilder()
.setPassword(PASSWORD)
.setClientCertificate(CertificateSamples.SAMPLE_CERT, DateTime.now(UTC))
.setClientCertificate(CertificateSamples.SAMPLE_CERT3, DateTime.now(UTC))
.setIpAddressAllowList(ImmutableList.of(new CidrAddressBlock(CLIENT_IP)))
.setState(ACTIVE)
.setAllowedTlds(ImmutableSet.of("tld"))
.build());
command.certificateChecker =
new CertificateChecker(
ImmutableSortedMap.of(START_OF_TIME, 825, DateTime.parse("2020-09-01T00:00:00Z"), 398),
30,
2048,
ImmutableSet.of("secp256r1", "secp384r1"),
fakeClock);
}
@Test
@ -64,7 +74,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
runCommand(
"--client=NewRegistrar",
"--password=" + PASSWORD,
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT),
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT3),
"--ip_address=" + CLIENT_IP);
}
@ -83,7 +93,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
runCommand(
"--client=NewRegistrar",
"--password=" + PASSWORD,
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT),
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT3),
"--ip_address=" + CLIENT_IP));
assertThat(thrown)
.hasMessageThat()
@ -99,7 +109,7 @@ class ValidateLoginCredentialsCommandTest extends CommandTestCase<ValidateLoginC
runCommand(
"--client=NewRegistrar",
"--password=" + new StringBuilder(PASSWORD).reverse(),
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT),
"--cert_file=" + getCertFilename(CertificateSamples.SAMPLE_CERT3),
"--ip_address=" + CLIENT_IP));
assertAboutEppExceptions().that(thrown).marshalsToXml();
}