mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
Convert Strings to X509 Certificates before validating (#948)
* Convert certificate strings to certificates * Format fixes * Revert "Format fixes" This reverts commit26f88bd313
. * Revert "Convert certificate strings to certificates" This reverts commit6d47ed2861
. * Convert strings to certs for validation * Add clarification comments * Add test to verify endoded cert from proxy * Add some helper methods * add tests for PEM with metadata * small changes * replace .com with .test
This commit is contained in:
parent
c8d878d084
commit
279f65b6cf
7 changed files with 229 additions and 26 deletions
|
@ -16,6 +16,7 @@ package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||||
import static google.registry.request.RequestParameters.extractOptionalHeader;
|
import static google.registry.request.RequestParameters.extractOptionalHeader;
|
||||||
|
import static google.registry.util.X509Utils.loadCertificate;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
@ -32,7 +33,11 @@ import google.registry.flows.certs.CertificateChecker.InsecureCertificateExcepti
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.request.Header;
|
import google.registry.request.Header;
|
||||||
import google.registry.util.CidrAddressBlock;
|
import google.registry.util.CidrAddressBlock;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -149,14 +154,31 @@ public class TlsCredentials implements TransportCredentials {
|
||||||
"Request from registrar %s did not include X-SSL-Full-Certificate.",
|
"Request from registrar %s did not include X-SSL-Full-Certificate.",
|
||||||
registrar.getClientId());
|
registrar.getClientId());
|
||||||
} else {
|
} else {
|
||||||
|
X509Certificate passedCert;
|
||||||
|
Optional<X509Certificate> storedCert;
|
||||||
|
Optional<X509Certificate> storedFailoverCert;
|
||||||
|
|
||||||
|
try {
|
||||||
|
storedCert = deserializePemCert(registrar.getClientCertificate());
|
||||||
|
storedFailoverCert = deserializePemCert(registrar.getFailoverClientCertificate());
|
||||||
|
passedCert = decodeCertString(clientCertificate.get());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO(Sarahbot@): remove this catch once we know it's working
|
||||||
|
logger.atWarning().log(
|
||||||
|
"Error converting certificate string to certificate for %s: %s",
|
||||||
|
registrar.getClientId(), e);
|
||||||
|
validateCertificateHash(registrar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the certificate is equal to the one on file for the registrar.
|
// Check if the certificate is equal to the one on file for the registrar.
|
||||||
if (clientCertificate.equals(registrar.getClientCertificate())
|
if (passedCert.equals(storedCert.orElse(null))
|
||||||
|| clientCertificate.equals(registrar.getFailoverClientCertificate())) {
|
|| passedCert.equals(storedFailoverCert.orElse(null))) {
|
||||||
// Check certificate for any requirement violations
|
// Check certificate for any requirement violations
|
||||||
// TODO(Sarahbot@): Throw exceptions instead of just logging once requirement enforcement
|
// TODO(Sarahbot@): Throw exceptions instead of just logging once requirement enforcement
|
||||||
// begins
|
// begins
|
||||||
try {
|
try {
|
||||||
certificateChecker.validateCertificate(clientCertificate.get());
|
certificateChecker.validateCertificate(passedCert);
|
||||||
} catch (InsecureCertificateException e) {
|
} catch (InsecureCertificateException e) {
|
||||||
// throw exception in unit tests and Sandbox
|
// throw exception in unit tests and Sandbox
|
||||||
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
||||||
|
@ -220,6 +242,22 @@ public class TlsCredentials implements TransportCredentials {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Converts a PEM formatted certificate string into an X509Certificate
|
||||||
|
private Optional<X509Certificate> deserializePemCert(Optional<String> certificateString)
|
||||||
|
throws CertificateException {
|
||||||
|
if (certificateString.isPresent()) {
|
||||||
|
return Optional.of(loadCertificate(certificateString.get()));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes the string representation of an encoded certificate back into an X509Certificate
|
||||||
|
private X509Certificate decodeCertString(String encodedCertString) throws CertificateException {
|
||||||
|
byte decodedCert[] = Base64.getDecoder().decode(encodedCertString);
|
||||||
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedCert);
|
||||||
|
return loadCertificate(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return toStringHelper(getClass())
|
return toStringHelper(getClass())
|
||||||
|
|
|
@ -90,7 +90,19 @@ public class CertificateChecker {
|
||||||
* exist.
|
* exist.
|
||||||
*/
|
*/
|
||||||
public void validateCertificate(String certificateString) throws InsecureCertificateException {
|
public void validateCertificate(String certificateString) throws InsecureCertificateException {
|
||||||
ImmutableSet<CertificateViolation> violations = checkCertificate(certificateString);
|
handleCertViolations(checkCertificate(certificateString));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the given certificate string for violations and throws an exception if any violations
|
||||||
|
* exist.
|
||||||
|
*/
|
||||||
|
public void validateCertificate(X509Certificate certificate) throws InsecureCertificateException {
|
||||||
|
handleCertViolations(checkCertificate(certificate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCertViolations(ImmutableSet<CertificateViolation> violations)
|
||||||
|
throws InsecureCertificateException {
|
||||||
if (!violations.isEmpty()) {
|
if (!violations.isEmpty()) {
|
||||||
String displayMessages =
|
String displayMessages =
|
||||||
violations.stream()
|
violations.stream()
|
||||||
|
|
|
@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||||
|
import static google.registry.util.X509Utils.encodeX509CertificateFromPemString;
|
||||||
import static google.registry.util.X509Utils.getCertificateHash;
|
import static google.registry.util.X509Utils.getCertificateHash;
|
||||||
import static google.registry.util.X509Utils.loadCertificate;
|
import static google.registry.util.X509Utils.loadCertificate;
|
||||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
|
@ -77,10 +78,11 @@ final class ValidateLoginCredentialsCommand implements CommandWithRemoteApi {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
clientCertificatePath == null || isNullOrEmpty(clientCertificateHash),
|
clientCertificatePath == null || isNullOrEmpty(clientCertificateHash),
|
||||||
"Can't specify both --cert_hash and --cert_file");
|
"Can't specify both --cert_hash and --cert_file");
|
||||||
String clientCertificate = "";
|
String encodedCertificate = "";
|
||||||
if (clientCertificatePath != null) {
|
if (clientCertificatePath != null) {
|
||||||
clientCertificate = new String(Files.readAllBytes(clientCertificatePath), US_ASCII);
|
String certificateString = new String(Files.readAllBytes(clientCertificatePath), US_ASCII);
|
||||||
clientCertificateHash = getCertificateHash(loadCertificate(clientCertificate));
|
encodedCertificate = encodeX509CertificateFromPemString(certificateString);
|
||||||
|
clientCertificateHash = getCertificateHash(loadCertificate(clientCertificatePath));
|
||||||
}
|
}
|
||||||
Registrar registrar =
|
Registrar registrar =
|
||||||
checkArgumentPresent(
|
checkArgumentPresent(
|
||||||
|
@ -88,7 +90,7 @@ final class ValidateLoginCredentialsCommand implements CommandWithRemoteApi {
|
||||||
new TlsCredentials(
|
new TlsCredentials(
|
||||||
true,
|
true,
|
||||||
Optional.ofNullable(clientCertificateHash),
|
Optional.ofNullable(clientCertificateHash),
|
||||||
Optional.ofNullable(clientCertificate),
|
Optional.ofNullable(encodedCertificate),
|
||||||
Optional.ofNullable(clientIpAddress),
|
Optional.ofNullable(clientIpAddress),
|
||||||
certificateChecker)
|
certificateChecker)
|
||||||
.validate(registrar, password);
|
.validate(registrar, password);
|
||||||
|
|
|
@ -18,6 +18,8 @@ import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
|
import static google.registry.util.X509Utils.encodeX509Certificate;
|
||||||
|
import static google.registry.util.X509Utils.encodeX509CertificateFromPemString;
|
||||||
import static org.joda.time.DateTimeZone.UTC;
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
@ -31,6 +33,7 @@ import google.registry.testing.CertificateSamples;
|
||||||
import google.registry.testing.SystemPropertyExtension;
|
import google.registry.testing.SystemPropertyExtension;
|
||||||
import google.registry.util.SelfSignedCaCertificate;
|
import google.registry.util.SelfSignedCaCertificate;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -67,6 +70,8 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
Logger.getLogger(TlsCredentials.class.getCanonicalName());
|
Logger.getLogger(TlsCredentials.class.getCanonicalName());
|
||||||
private final TestLogHandler handler = new TestLogHandler();
|
private final TestLogHandler handler = new TestLogHandler();
|
||||||
|
|
||||||
|
private String encodedCertString;
|
||||||
|
|
||||||
void setCredentials(String clientCertificateHash, String clientCertificate) {
|
void setCredentials(String clientCertificateHash, String clientCertificate) {
|
||||||
setTransportCredentials(
|
setTransportCredentials(
|
||||||
new TlsCredentials(
|
new TlsCredentials(
|
||||||
|
@ -78,7 +83,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() {
|
void beforeEach() throws CertificateException {
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
|
@ -91,18 +96,19 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
.setClientCertificate(CertificateSamples.SAMPLE_CERT2, DateTime.now(UTC))
|
.setClientCertificate(CertificateSamples.SAMPLE_CERT2, DateTime.now(UTC))
|
||||||
.build());
|
.build());
|
||||||
loggerToIntercept.addHandler(handler);
|
loggerToIntercept.addHandler(handler);
|
||||||
|
encodedCertString = encodeX509CertificateFromPemString(CertificateSamples.SAMPLE_CERT3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoginLogout() throws Exception {
|
void testLoginLogout() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
|
setCredentials(null, encodedCertString);
|
||||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||||
assertThatLogoutSucceeds();
|
assertThatLogoutSucceeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLogin_wrongPasswordFails() throws Exception {
|
void testLogin_wrongPasswordFails() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
|
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, encodedCertString);
|
||||||
// For TLS login, we also check the epp xml password.
|
// For TLS login, we also check the epp xml password.
|
||||||
assertThatLogin("NewRegistrar", "incorrect")
|
assertThatLogin("NewRegistrar", "incorrect")
|
||||||
.hasResponse(
|
.hasResponse(
|
||||||
|
@ -112,7 +118,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMultiLogin() throws Exception {
|
void testMultiLogin() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
|
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, encodedCertString);
|
||||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||||
assertThatLogoutSucceeds();
|
assertThatLogoutSucceeds();
|
||||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||||
|
@ -126,7 +132,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNonAuthedLogin_fails() throws Exception {
|
void testNonAuthedLogin_fails() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
|
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, encodedCertString);
|
||||||
assertThatLogin("TheRegistrar", "password2")
|
assertThatLogin("TheRegistrar", "password2")
|
||||||
.hasResponse(
|
.hasResponse(
|
||||||
"response_error.xml",
|
"response_error.xml",
|
||||||
|
@ -155,7 +161,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGoodPrimaryCertificate() throws Exception {
|
void testGoodPrimaryCertificate() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
|
setCredentials(null, encodedCertString);
|
||||||
DateTime now = DateTime.now(UTC);
|
DateTime now = DateTime.now(UTC);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
|
@ -168,7 +174,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGoodFailoverCertificate() throws Exception {
|
void testGoodFailoverCertificate() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
|
setCredentials(null, encodedCertString);
|
||||||
DateTime now = DateTime.now(UTC);
|
DateTime now = DateTime.now(UTC);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
|
@ -181,7 +187,7 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMissingPrimaryCertificateButHasFailover_usesFailover() throws Exception {
|
void testMissingPrimaryCertificateButHasFailover_usesFailover() throws Exception {
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT3_HASH, CertificateSamples.SAMPLE_CERT3);
|
setCredentials(null, encodedCertString);
|
||||||
DateTime now = DateTime.now(UTC);
|
DateTime now = DateTime.now(UTC);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
|
@ -210,8 +216,9 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCertificateDoesNotMeetRequirements_fails() throws Exception {
|
void testCertificateDoesNotMeetRequirements_fails() throws Exception {
|
||||||
|
String proxyEncoded = encodeX509CertificateFromPemString(CertificateSamples.SAMPLE_CERT);
|
||||||
// SAMPLE_CERT has a validity period that is too long
|
// SAMPLE_CERT has a validity period that is too long
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
|
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, proxyEncoded);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
|
@ -232,7 +239,6 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCertificateDoesNotMeetMultipleRequirements_fails() throws Exception {
|
void testCertificateDoesNotMeetMultipleRequirements_fails() throws Exception {
|
||||||
|
|
||||||
X509Certificate certificate =
|
X509Certificate certificate =
|
||||||
SelfSignedCaCertificate.create(
|
SelfSignedCaCertificate.create(
|
||||||
"test", clock.nowUtc().plusDays(100), clock.nowUtc().plusDays(5000))
|
"test", clock.nowUtc().plusDays(100), clock.nowUtc().plusDays(5000))
|
||||||
|
@ -244,8 +250,10 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
pw.writeObject(generator);
|
pw.writeObject(generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String proxyEncoded = encodeX509Certificate(certificate);
|
||||||
|
|
||||||
// SAMPLE_CERT has a validity period that is too long
|
// SAMPLE_CERT has a validity period that is too long
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, sw.toString());
|
setCredentials(null, proxyEncoded);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
|
@ -270,7 +278,8 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
void testCertificateDoesNotMeetRequirementsInProduction_succeeds() throws Exception {
|
void testCertificateDoesNotMeetRequirementsInProduction_succeeds() throws Exception {
|
||||||
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
|
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
|
||||||
// SAMPLE_CERT has a validity period that is too long
|
// SAMPLE_CERT has a validity period that is too long
|
||||||
setCredentials(CertificateSamples.SAMPLE_CERT_HASH, CertificateSamples.SAMPLE_CERT);
|
String proxyEncoded = encodeX509CertificateFromPemString(CertificateSamples.SAMPLE_CERT);
|
||||||
|
setCredentials(null, proxyEncoded);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadRegistrar("NewRegistrar")
|
loadRegistrar("NewRegistrar")
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
|
@ -288,4 +297,61 @@ class EppLoginTlsTest extends EppTestCase {
|
||||||
+ " Certificate validity period is too long; it must be less than or equal to 398"
|
+ " Certificate validity period is too long; it must be less than or equal to 398"
|
||||||
+ " days.");
|
+ " days.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegistrarCertificateContainsExtraMetadata_succeeds() throws Exception {
|
||||||
|
String certPem =
|
||||||
|
String.format(
|
||||||
|
"Bag Attributes\n"
|
||||||
|
+ " localKeyID: 1F 1C 3A 3A 4C 03 EC C4 BC 7A C3 21 A9 F2 13 66 21 B8 7B 26 \n"
|
||||||
|
+ "subject=/C=US/ST=New York/L=New"
|
||||||
|
+ " York/O=Test/OU=ABC/CN=tester.test/emailAddress=test-certificate@test.test\n"
|
||||||
|
+ "issuer=/C=US/ST=NY/L=NYC/O=ABC/OU=TEST CA/CN=TEST"
|
||||||
|
+ " CA/emailAddress=testing@test.test\n"
|
||||||
|
+ "%s",
|
||||||
|
CertificateSamples.SAMPLE_CERT3);
|
||||||
|
|
||||||
|
setCredentials(null, encodeX509CertificateFromPemString(certPem));
|
||||||
|
DateTime now = DateTime.now(UTC);
|
||||||
|
persistResource(
|
||||||
|
loadRegistrar("NewRegistrar")
|
||||||
|
.asBuilder()
|
||||||
|
.setClientCertificate(certPem, now)
|
||||||
|
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||||
|
.build());
|
||||||
|
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegistrarCertificateContainsExtraMetadataAndViolations_fails() throws Exception {
|
||||||
|
String certPem =
|
||||||
|
String.format(
|
||||||
|
"Bag Attributes\n"
|
||||||
|
+ " localKeyID: 1F 1C 3A 3A 4C 03 EC C4 BC 7A C3 21 A9 F2 13 66 21 B8 7B 26 \n"
|
||||||
|
+ "subject=/C=US/ST=New York/L=New"
|
||||||
|
+ " York/O=Test/OU=ABC/CN=tester.test/emailAddress=test-certificate@test.test\n"
|
||||||
|
+ "issuer=/C=US/ST=NY/L=NYC/O=ABC/OU=TEST CA/CN=TEST"
|
||||||
|
+ " CA/emailAddress=testing@test.test\n"
|
||||||
|
+ "%s",
|
||||||
|
CertificateSamples.SAMPLE_CERT);
|
||||||
|
|
||||||
|
setCredentials(null, encodeX509CertificateFromPemString(certPem));
|
||||||
|
DateTime now = DateTime.now(UTC);
|
||||||
|
persistResource(
|
||||||
|
loadRegistrar("NewRegistrar")
|
||||||
|
.asBuilder()
|
||||||
|
.setClientCertificate(certPem, now)
|
||||||
|
.setFailoverClientCertificate(CertificateSamples.SAMPLE_CERT2, now)
|
||||||
|
.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."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package google.registry.flows.session;
|
||||||
|
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
|
import static google.registry.util.X509Utils.encodeX509CertificateFromPemString;
|
||||||
import static org.joda.time.DateTimeZone.UTC;
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
@ -30,9 +31,18 @@ import google.registry.flows.certs.CertificateChecker;
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.testing.CertificateSamples;
|
import google.registry.testing.CertificateSamples;
|
||||||
import google.registry.util.CidrAddressBlock;
|
import google.registry.util.CidrAddressBlock;
|
||||||
|
import google.registry.util.SelfSignedCaCertificate;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
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;
|
||||||
|
|
||||||
/** Unit tests for {@link LoginFlow} when accessed via a TLS transport. */
|
/** Unit tests for {@link LoginFlow} when accessed via a TLS transport. */
|
||||||
public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||||
|
@ -54,6 +64,12 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||||
2048,
|
2048,
|
||||||
ImmutableSet.of("secp256r1", "secp384r1"),
|
ImmutableSet.of("secp256r1", "secp384r1"),
|
||||||
clock);
|
clock);
|
||||||
|
private Optional<String> encodedCertString;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() throws CertificateException {
|
||||||
|
encodedCertString = Optional.of(encodeX509CertificateFromPemString(GOOD_CERT.get()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Registrar.Builder getRegistrarBuilder() {
|
protected Registrar.Builder getRegistrarBuilder() {
|
||||||
|
@ -66,7 +82,36 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_withGoodCredentials() throws Exception {
|
void testSuccess_withGoodCredentials() throws Exception {
|
||||||
persistResource(getRegistrarBuilder().build());
|
persistResource(getRegistrarBuilder().build());
|
||||||
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IP, certificateChecker);
|
credentials =
|
||||||
|
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IP, certificateChecker);
|
||||||
|
doSuccessfulTest("login_valid.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_withNewlyConstructedCertificate() throws Exception {
|
||||||
|
X509Certificate certificate =
|
||||||
|
SelfSignedCaCertificate.create(
|
||||||
|
"test", clock.nowUtc().minusDays(100), clock.nowUtc().plusDays(150))
|
||||||
|
.cert();
|
||||||
|
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
try (PemWriter pw = new PemWriter(sw)) {
|
||||||
|
PemObjectGenerator generator = new JcaMiscPEMGenerator(certificate);
|
||||||
|
pw.writeObject(generator);
|
||||||
|
}
|
||||||
|
|
||||||
|
persistResource(
|
||||||
|
super.getRegistrarBuilder()
|
||||||
|
.setClientCertificate(sw.toString(), DateTime.now(UTC))
|
||||||
|
.setIpAddressAllowList(
|
||||||
|
ImmutableList.of(
|
||||||
|
CidrAddressBlock.create(InetAddresses.forString(GOOD_IP.get()), 32)))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
String encodedCertificate = Base64.getEncoder().encodeToString(certificate.getEncoded());
|
||||||
|
credentials =
|
||||||
|
new TlsCredentials(
|
||||||
|
true, Optional.empty(), Optional.of(encodedCertificate), GOOD_IP, certificateChecker);
|
||||||
doSuccessfulTest("login_valid.xml");
|
doSuccessfulTest("login_valid.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +123,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||||
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
|
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
|
||||||
.build());
|
.build());
|
||||||
credentials =
|
credentials =
|
||||||
new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IPV6, certificateChecker);
|
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IPV6, certificateChecker);
|
||||||
doSuccessfulTest("login_valid.xml");
|
doSuccessfulTest("login_valid.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +135,7 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||||
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
|
ImmutableList.of(CidrAddressBlock.create("2001:db8:0:0:0:0:1:1/32")))
|
||||||
.build());
|
.build());
|
||||||
credentials =
|
credentials =
|
||||||
new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IPV6, certificateChecker);
|
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IPV6, certificateChecker);
|
||||||
doSuccessfulTest("login_valid.xml");
|
doSuccessfulTest("login_valid.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,14 +145,18 @@ public class LoginFlowViaTlsTest extends LoginFlowTestCase {
|
||||||
getRegistrarBuilder()
|
getRegistrarBuilder()
|
||||||
.setIpAddressAllowList(ImmutableList.of(CidrAddressBlock.create("192.168.1.255/24")))
|
.setIpAddressAllowList(ImmutableList.of(CidrAddressBlock.create("192.168.1.255/24")))
|
||||||
.build());
|
.build());
|
||||||
credentials = new TlsCredentials(true, GOOD_CERT_HASH, GOOD_CERT, GOOD_IP, certificateChecker);
|
credentials =
|
||||||
|
new TlsCredentials(true, GOOD_CERT_HASH, encodedCertString, GOOD_IP, certificateChecker);
|
||||||
doSuccessfulTest("login_valid.xml");
|
doSuccessfulTest("login_valid.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailure_incorrectClientCertificateHash() {
|
void testFailure_incorrectClientCertificateHash() throws Exception {
|
||||||
persistResource(getRegistrarBuilder().build());
|
persistResource(getRegistrarBuilder().build());
|
||||||
credentials = new TlsCredentials(true, BAD_CERT_HASH, BAD_CERT, GOOD_IP, certificateChecker);
|
String proxyEncoded = encodeX509CertificateFromPemString(BAD_CERT.get());
|
||||||
|
credentials =
|
||||||
|
new TlsCredentials(
|
||||||
|
true, BAD_CERT_HASH, Optional.of(proxyEncoded), GOOD_IP, certificateChecker);
|
||||||
doFailingTest("login_valid.xml", BadRegistrarCertificateException.class);
|
doFailingTest("login_valid.xml", BadRegistrarCertificateException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import static google.registry.proxy.TestUtils.assertHttpRequestEquivalent;
|
||||||
import static google.registry.proxy.TestUtils.makeEppHttpResponse;
|
import static google.registry.proxy.TestUtils.makeEppHttpResponse;
|
||||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||||
import static google.registry.util.X509Utils.getCertificateHash;
|
import static google.registry.util.X509Utils.getCertificateHash;
|
||||||
|
import static google.registry.util.X509Utils.loadCertificate;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
@ -47,6 +48,7 @@ import io.netty.util.concurrent.Promise;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -239,6 +241,23 @@ class EppServiceHandlerTest {
|
||||||
assertThat(channel.isActive()).isTrue();
|
assertThat(channel.isActive()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_requestContainsEncodedCertificate() throws Exception {
|
||||||
|
setHandshakeSuccess();
|
||||||
|
// First inbound message is hello.
|
||||||
|
channel.readInbound();
|
||||||
|
String content = "<epp>stuff</epp>";
|
||||||
|
channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8)));
|
||||||
|
FullHttpRequest request = channel.readInbound();
|
||||||
|
assertThat(request).isEqualTo(makeEppHttpRequestWithCertificate(content));
|
||||||
|
String encodedCert = request.headers().get("X-SSL-Full-Certificate");
|
||||||
|
assertThat(encodedCert).isNotEqualTo(SAMPLE_CERT);
|
||||||
|
X509Certificate decodedCert =
|
||||||
|
loadCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(encodedCert)));
|
||||||
|
X509Certificate pemCert = loadCertificate(SAMPLE_CERT);
|
||||||
|
assertThat(decodedCert).isEqualTo(pemCert);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_sendCertificateOnlyBeforeLogin() throws Exception {
|
void testSuccess_sendCertificateOnlyBeforeLogin() throws Exception {
|
||||||
setHandshakeSuccess();
|
setHandshakeSuccess();
|
||||||
|
|
|
@ -32,6 +32,7 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.cert.CRLException;
|
import java.security.cert.CRLException;
|
||||||
import java.security.cert.CRLReason;
|
import java.security.cert.CRLReason;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.CertificateParsingException;
|
import java.security.cert.CertificateParsingException;
|
||||||
|
@ -39,6 +40,7 @@ import java.security.cert.CertificateRevokedException;
|
||||||
import java.security.cert.X509CRL;
|
import java.security.cert.X509CRL;
|
||||||
import java.security.cert.X509CRLEntry;
|
import java.security.cert.X509CRLEntry;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -177,5 +179,20 @@ public final class X509Utils {
|
||||||
newCrl.verify(rootCert.getPublicKey());
|
newCrl.verify(rootCert.getPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Constructs an X.509 certificate from a PEM string and encodes it. */
|
||||||
|
public static String encodeX509CertificateFromPemString(String certificateString)
|
||||||
|
throws CertificateException {
|
||||||
|
return encodeX509Certificate(loadCertificate(certificateString));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes an X.509 certificate in the same form that the proxy encodes a certificate before
|
||||||
|
* passing it via an HTTP header.
|
||||||
|
*/
|
||||||
|
public static String encodeX509Certificate(X509Certificate certificate)
|
||||||
|
throws CertificateEncodingException {
|
||||||
|
return Base64.getEncoder().encodeToString(certificate.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
private X509Utils() {}
|
private X509Utils() {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue