diff --git a/proxy/src/main/java/google/registry/proxy/handler/EppServiceHandler.java b/proxy/src/main/java/google/registry/proxy/handler/EppServiceHandler.java index 20a2bdf18..ebb624800 100644 --- a/proxy/src/main/java/google/registry/proxy/handler/EppServiceHandler.java +++ b/proxy/src/main/java/google/registry/proxy/handler/EppServiceHandler.java @@ -34,7 +34,9 @@ import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Promise; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.util.Base64; import java.util.function.Supplier; /** Handler that processes EPP protocol logic. */ @@ -52,18 +54,26 @@ public class EppServiceHandler extends HttpsRelayServiceHandler { /** Name of the HTTP header that stores the client certificate hash. */ public static final String SSL_CLIENT_CERTIFICATE_HASH_FIELD = "X-SSL-Certificate"; + /** Name of the HTTP header that stores the full client certificate. */ + public static final String SSL_CLIENT_FULL_CERTIFICATE_FIELD = "X-SSL-Full-Certificate"; + /** Name of the HTTP header that stores the client IP address. */ public static final String FORWARDED_FOR_FIELD = "X-Forwarded-For"; /** Name of the HTTP header that indicates if the EPP session should be closed. */ public static final String EPP_SESSION_FIELD = "Epp-Session"; + /** Name of the HTTP header that indicates a successful login has occurred. */ + public static final String EPP_LOGGED_IN_FIELD = "Logged-In"; + public static final String EPP_CONTENT_TYPE = "application/epp+xml"; private final byte[] helloBytes; private String sslClientCertificateHash; + private X509Certificate sslClientCertificate; private String clientAddress; + private boolean isLoggedIn = false; public EppServiceHandler( String relayHost, @@ -103,7 +113,8 @@ public class EppServiceHandler extends HttpsRelayServiceHandler { .addListener( (Promise promise) -> { if (promise.isSuccess()) { - sslClientCertificateHash = getCertificateHash(promise.get()); + sslClientCertificate = promise.get(); + sslClientCertificateHash = getCertificateHash(sslClientCertificate); // Set the client cert hash key attribute for both this channel, // used for collecting metrics on specific clients. ctx.channel().attr(CLIENT_CERTIFICATE_HASH_KEY).set(sslClientCertificateHash); @@ -132,6 +143,17 @@ public class EppServiceHandler extends HttpsRelayServiceHandler { .set(FORWARDED_FOR_FIELD, clientAddress) .set(HttpHeaderNames.CONTENT_TYPE, EPP_CONTENT_TYPE) .set(HttpHeaderNames.ACCEPT, EPP_CONTENT_TYPE); + if (!isLoggedIn) { + try { + request + .headers() + .set( + SSL_CLIENT_FULL_CERTIFICATE_FIELD, + Base64.getEncoder().encodeToString(sslClientCertificate.getEncoded())); + } catch (CertificateEncodingException e) { + throw new RuntimeException("Cannot encode client certificate", e); + } + } return request; } @@ -141,9 +163,13 @@ public class EppServiceHandler extends HttpsRelayServiceHandler { checkArgument(msg instanceof HttpResponse); HttpResponse response = (HttpResponse) msg; String sessionAliveValue = response.headers().get(EPP_SESSION_FIELD); + String loginValue = response.headers().get(EPP_LOGGED_IN_FIELD); if (sessionAliveValue != null && sessionAliveValue.equals("close")) { promise.addListener(ChannelFutureListener.CLOSE); } + if (loginValue != null && loginValue.equals("true")) { + isLoggedIn = true; + } super.write(ctx, msg, promise); } } diff --git a/proxy/src/test/java/google/registry/proxy/EppProtocolModuleTest.java b/proxy/src/test/java/google/registry/proxy/EppProtocolModuleTest.java index 98e52e0f2..e88e5fe89 100644 --- a/proxy/src/test/java/google/registry/proxy/EppProtocolModuleTest.java +++ b/proxy/src/test/java/google/registry/proxy/EppProtocolModuleTest.java @@ -16,6 +16,7 @@ package google.registry.proxy; import static com.google.common.truth.Truth.assertThat; import static google.registry.networking.handler.SslServerInitializer.CLIENT_CERTIFICATE_PROMISE_KEY; +import static google.registry.proxy.TestUtils.SAMPLE_CERT; import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY; import static google.registry.util.ResourceUtils.readResourceBytes; import static google.registry.util.X509Utils.getCertificateHash; @@ -25,7 +26,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.base.Throwables; import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException; import google.registry.testing.FakeClock; -import google.registry.util.SelfSignedCaCertificate; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; @@ -36,6 +36,8 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; import io.netty.util.concurrent.Promise; +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -96,8 +98,8 @@ class EppProtocolModuleTest extends ProtocolModuleTest { return buffer; } - private FullHttpRequest makeEppHttpRequest(byte[] content, Cookie... cookies) { - return TestUtils.makeEppHttpRequest( + private FullHttpRequest makeEppHttpRequestWithCertificate(byte[] content, Cookie... cookies) { + return TestUtils.makeEppHttpRequestWithCertificate( new String(content, UTF_8), PROXY_CONFIG.epp.relayHost, PROXY_CONFIG.epp.relayPath, @@ -120,7 +122,10 @@ class EppProtocolModuleTest extends ProtocolModuleTest { @Override void beforeEach() throws Exception { testComponent = makeTestComponent(new FakeClock()); - certificate = SelfSignedCaCertificate.create().cert(); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + certificate = + (X509Certificate) + cf.generateCertificate(new ByteArrayInputStream(SAMPLE_CERT.getBytes(UTF_8))); initializeChannel( ch -> { ch.attr(REMOTE_ADDRESS_KEY).set(CLIENT_ADDRESS); @@ -134,13 +139,15 @@ class EppProtocolModuleTest extends ProtocolModuleTest { @Test void testSuccess_singleFrameInboundMessage() throws Exception { // First inbound message is hello. - assertThat((FullHttpRequest) channel.readInbound()).isEqualTo(makeEppHttpRequest(HELLO_BYTES)); + assertThat((FullHttpRequest) channel.readInbound()) + .isEqualTo(makeEppHttpRequestWithCertificate(HELLO_BYTES)); byte[] inputBytes = readResourceBytes(getClass(), "login.xml").read(); // Verify inbound message is as expected. assertThat(channel.writeInbound(getByteBufFromContent(inputBytes))).isTrue(); - assertThat((FullHttpRequest) channel.readInbound()).isEqualTo(makeEppHttpRequest(inputBytes)); + assertThat((FullHttpRequest) channel.readInbound()) + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes)); // Nothing more to read. assertThat((Object) channel.readInbound()).isNull(); @@ -161,8 +168,10 @@ class EppProtocolModuleTest extends ProtocolModuleTest { Unpooled.wrappedBuffer( getByteBufFromContent(inputBytes1), getByteBufFromContent(inputBytes2)))) .isTrue(); - assertThat((FullHttpRequest) channel.readInbound()).isEqualTo(makeEppHttpRequest(inputBytes1)); - assertThat((FullHttpRequest) channel.readInbound()).isEqualTo(makeEppHttpRequest(inputBytes2)); + assertThat((FullHttpRequest) channel.readInbound()) + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes1)); + assertThat((FullHttpRequest) channel.readInbound()) + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes2)); // Nothing more to read. assertThat((Object) channel.readInbound()).isNull(); @@ -186,11 +195,13 @@ class EppProtocolModuleTest extends ProtocolModuleTest { // The second frame contains the first message, and part of the second message. assertThat(channel.writeInbound(inputBuffer.readBytes(inputBytes2.length))).isTrue(); - assertThat((FullHttpRequest) channel.readInbound()).isEqualTo(makeEppHttpRequest(inputBytes1)); + assertThat((FullHttpRequest) channel.readInbound()) + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes1)); // The third frame contains the rest of the second message. assertThat(channel.writeInbound(inputBuffer)).isTrue(); - assertThat((FullHttpRequest) channel.readInbound()).isEqualTo(makeEppHttpRequest(inputBytes2)); + assertThat((FullHttpRequest) channel.readInbound()) + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes2)); // Nothing more to read. assertThat((Object) channel.readInbound()).isNull(); @@ -252,7 +263,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest { byte[] inputBytes1 = readResourceBytes(getClass(), "logout.xml").read(); assertThat(channel.writeInbound(getByteBufFromContent(inputBytes1))).isTrue(); assertThat((FullHttpRequest) channel.readInbound()) - .isEqualTo(makeEppHttpRequest(inputBytes1, cookie1, cookie2)); + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes1, cookie1, cookie2)); // Second outbound message change cookies. byte[] outputBytes2 = readResourceBytes(getClass(), "logout_response.xml").read(); @@ -267,7 +278,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest { byte[] inputBytes2 = readResourceBytes(getClass(), "login.xml").read(); assertThat(channel.writeInbound(getByteBufFromContent(inputBytes2))).isTrue(); assertThat((FullHttpRequest) channel.readInbound()) - .isEqualTo(makeEppHttpRequest(inputBytes2, cookie1, cookie2, cookie3)); + .isEqualTo(makeEppHttpRequestWithCertificate(inputBytes2, cookie1, cookie2, cookie3)); // Nothing more to write or read. assertThat((Object) channel.readOutbound()).isNull(); diff --git a/proxy/src/test/java/google/registry/proxy/TestUtils.java b/proxy/src/test/java/google/registry/proxy/TestUtils.java index 42a104339..831f7670a 100644 --- a/proxy/src/test/java/google/registry/proxy/TestUtils.java +++ b/proxy/src/test/java/google/registry/proxy/TestUtils.java @@ -36,6 +36,54 @@ import io.netty.handler.codec.http.cookie.ServerCookieEncoder; /** Utility class for various helper methods used in testing. */ public class TestUtils { + public static final String SAMPLE_CERT = + "-----BEGIN CERTIFICATE-----\n" + + "MIIDvTCCAqWgAwIBAgIJAK/PgPT0jTwRMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV\n" + + "BAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxDzAN\n" + + "BgNVBAoMBkdvb2dsZTEdMBsGA1UECwwUZG9tYWluLXJlZ2lzdHJ5LXRlc3QxEDAO\n" + + "BgNVBAMMB2NsaWVudDEwHhcNMTUwODI2MTkxODA4WhcNNDMwMTExMTkxODA4WjB1\n" + + "MQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZ\n" + + "b3JrMQ8wDQYDVQQKDAZHb29nbGUxHTAbBgNVBAsMFGRvbWFpbi1yZWdpc3RyeS10\n" + + "ZXN0MRAwDgYDVQQDDAdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + + "CgKCAQEAvoE/IoFJyzb0dU4NFhL8FYgy+B/GnUd5aA66CMx5xKRMbEAtIgxU8TTO\n" + + "W+9jdTsE00Grk3Ct4KdY73CYW+6IFXL4O0K/m5S+uajh+I2UMVZJV38RAIqNxue0\n" + + "Egv9M4haSsCVIPcX9b+6McywfYSF1bzPb2Gb2FAQO7Jb0BjlPhPMIROCrbG40qPg\n" + + "LWrl33dz+O52kO+DyZEzHqI55xH6au77sMITsJe+X23lzQcMFUUm8moiOw0EKrj/\n" + + "GaMTZLHP46BCRoJDAPTNx55seIwgAHbKA2VVtqrvmA2XYJQA6ipdhfKRoJFy8Z8H\n" + + "DYsorGtazQL2HhF/5uJD25z1m5eQHQIDAQABo1AwTjAdBgNVHQ4EFgQUParEmiSR\n" + + "U/Oqy8hr7k+MBKhZwVkwHwYDVR0jBBgwFoAUParEmiSRU/Oqy8hr7k+MBKhZwVkw\n" + + "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAojsUhF6PtZrStnHBFWNR\n" + + "ryzvANB8krZlYeX9Hkqn8zIVfAkpbVmL8aZQ7yj17jSpw47PQh3x5gwA9yc/SS0G\n" + + "E1rGuxYH02UGbua8G0+vviSQfLtskPQzK7EIR63WNhHEo/Q9umLJkZ0LguWEBf3L\n" + + "q8CoXv2i/RNvqVPcTNp/zCKXJZAa8wAjNRJs834AZj4k5xwyYZ3F8D5PGz+YMOmV\n" + + "M9Qd+NdXSC/Qn7HQzFhE8p5elBV35P8oX5dXEfn0S7zOXDenp5JvvLoggOWOcKsq\n" + + "KiWDQrsT+TMKmHL94/h4t7FghtQLMzY5SGYJsYTv/LG8tewrz6KRb/Wj3JNojyEw\n" + + "Ug==\n" + + "-----END CERTIFICATE-----\n"; + + public static final String SAMPLE_CERT_ENCODED = + "MIIDvTCCAqWgAwIBAgIJAK/PgPT0jTwRMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV" + + "BAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxDzAN" + + "BgNVBAoMBkdvb2dsZTEdMBsGA1UECwwUZG9tYWluLXJlZ2lzdHJ5LXRlc3QxEDAO" + + "BgNVBAMMB2NsaWVudDEwHhcNMTUwODI2MTkxODA4WhcNNDMwMTExMTkxODA4WjB1" + + "MQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZ" + + "b3JrMQ8wDQYDVQQKDAZHb29nbGUxHTAbBgNVBAsMFGRvbWFpbi1yZWdpc3RyeS10" + + "ZXN0MRAwDgYDVQQDDAdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" + + "CgKCAQEAvoE/IoFJyzb0dU4NFhL8FYgy+B/GnUd5aA66CMx5xKRMbEAtIgxU8TTO" + + "W+9jdTsE00Grk3Ct4KdY73CYW+6IFXL4O0K/m5S+uajh+I2UMVZJV38RAIqNxue0" + + "Egv9M4haSsCVIPcX9b+6McywfYSF1bzPb2Gb2FAQO7Jb0BjlPhPMIROCrbG40qPg" + + "LWrl33dz+O52kO+DyZEzHqI55xH6au77sMITsJe+X23lzQcMFUUm8moiOw0EKrj/" + + "GaMTZLHP46BCRoJDAPTNx55seIwgAHbKA2VVtqrvmA2XYJQA6ipdhfKRoJFy8Z8H" + + "DYsorGtazQL2HhF/5uJD25z1m5eQHQIDAQABo1AwTjAdBgNVHQ4EFgQUParEmiSR" + + "U/Oqy8hr7k+MBKhZwVkwHwYDVR0jBBgwFoAUParEmiSRU/Oqy8hr7k+MBKhZwVkw" + + "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAojsUhF6PtZrStnHBFWNR" + + "ryzvANB8krZlYeX9Hkqn8zIVfAkpbVmL8aZQ7yj17jSpw47PQh3x5gwA9yc/SS0G" + + "E1rGuxYH02UGbua8G0+vviSQfLtskPQzK7EIR63WNhHEo/Q9umLJkZ0LguWEBf3L" + + "q8CoXv2i/RNvqVPcTNp/zCKXJZAa8wAjNRJs834AZj4k5xwyYZ3F8D5PGz+YMOmV" + + "M9Qd+NdXSC/Qn7HQzFhE8p5elBV35P8oX5dXEfn0S7zOXDenp5JvvLoggOWOcKsq" + + "KiWDQrsT+TMKmHL94/h4t7FghtQLMzY5SGYJsYTv/LG8tewrz6KRb/Wj3JNojyEw" + + "Ug=="; + public static FullHttpRequest makeHttpPostRequest(String content, String host, String path) { ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII)); FullHttpRequest request = @@ -101,6 +149,21 @@ public class TestUtils { return request; } + public static FullHttpRequest makeEppHttpRequestWithCertificate( + String content, + String host, + String path, + String accessToken, + String sslClientCertificateHash, + String clientAddress, + Cookie... cookies) { + FullHttpRequest request = + makeEppHttpRequest( + content, host, path, accessToken, sslClientCertificateHash, clientAddress, cookies); + request.headers().set("X-SSL-Full-Certificate", SAMPLE_CERT_ENCODED); + return request; + } + public static FullHttpResponse makeWhoisHttpResponse(String content, HttpResponseStatus status) { FullHttpResponse response = makeHttpResponse(content, status); response.headers().set("content-type", "text/plain"); diff --git a/proxy/src/test/java/google/registry/proxy/handler/EppServiceHandlerTest.java b/proxy/src/test/java/google/registry/proxy/handler/EppServiceHandlerTest.java index c60fcd26a..ab389d14c 100644 --- a/proxy/src/test/java/google/registry/proxy/handler/EppServiceHandlerTest.java +++ b/proxy/src/test/java/google/registry/proxy/handler/EppServiceHandlerTest.java @@ -16,6 +16,7 @@ package google.registry.proxy.handler; import static com.google.common.truth.Truth.assertThat; import static google.registry.networking.handler.SslServerInitializer.CLIENT_CERTIFICATE_PROMISE_KEY; +import static google.registry.proxy.TestUtils.SAMPLE_CERT; import static google.registry.proxy.TestUtils.assertHttpRequestEquivalent; import static google.registry.proxy.TestUtils.makeEppHttpResponse; import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY; @@ -43,6 +44,8 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; import io.netty.util.concurrent.Promise; +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -109,9 +112,23 @@ class EppServiceHandlerTest { cookies); } + private FullHttpRequest makeEppHttpRequestWithCertificate(String content, Cookie... cookies) { + return TestUtils.makeEppHttpRequestWithCertificate( + content, + RELAY_HOST, + RELAY_PATH, + ACCESS_TOKEN, + getCertificateHash(clientCertificate), + CLIENT_ADDRESS, + cookies); + } + @BeforeEach void beforeEach() throws Exception { - clientCertificate = SelfSignedCaCertificate.create().cert(); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + clientCertificate = + (X509Certificate) + cf.generateCertificate(new ByteArrayInputStream(SAMPLE_CERT.getBytes(UTF_8))); channel = setUpNewChannel(eppServiceHandler); } @@ -194,7 +211,7 @@ class EppServiceHandlerTest { setHandshakeSuccess(); // hello bytes should be passed to the next handler. FullHttpRequest helloRequest = channel.readInbound(); - assertThat(helloRequest).isEqualTo(makeEppHttpRequest(HELLO)); + assertThat(helloRequest).isEqualTo(makeEppHttpRequestWithCertificate(HELLO)); // Nothing further to pass to the next handler. assertThat((Object) channel.readInbound()).isNull(); assertThat(channel.isActive()).isTrue(); @@ -216,12 +233,34 @@ class EppServiceHandlerTest { String content = "stuff"; channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8))); FullHttpRequest request = channel.readInbound(); - assertThat(request).isEqualTo(makeEppHttpRequest(content)); + assertThat(request).isEqualTo(makeEppHttpRequestWithCertificate(content)); // Nothing further to pass to the next handler. assertThat((Object) channel.readInbound()).isNull(); assertThat(channel.isActive()).isTrue(); } + @Test + void testSuccess_sendCertificateOnlyBeforeLogin() throws Exception { + setHandshakeSuccess(); + // First inbound message is hello. + channel.readInbound(); + String content = "stuff"; + channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8))); + FullHttpRequest request = channel.readInbound(); + assertThat(request).isEqualTo(makeEppHttpRequestWithCertificate(content)); + // Receive response indicating session is logged in + HttpResponse response = makeEppHttpResponse(content, HttpResponseStatus.OK); + response.headers().set("Logged-In", "true"); + // Send another inbound message after login + channel.writeOutbound(response); + channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8))); + request = channel.readInbound(); + // Second request should not have full certificate + assertThat(request).isEqualTo(makeEppHttpRequest(content)); + assertThat((Object) channel.readInbound()).isNull(); + assertThat(channel.isActive()).isTrue(); + } + @Test void testSuccess_sendResponseToNextHandler() throws Exception { setHandshakeSuccess(); @@ -294,7 +333,8 @@ class EppServiceHandlerTest { String requestContent = "request"; channel.writeInbound(Unpooled.wrappedBuffer(requestContent.getBytes(UTF_8))); FullHttpRequest request = channel.readInbound(); - assertHttpRequestEquivalent(request, makeEppHttpRequest(requestContent, cookie1, cookie2)); + assertHttpRequestEquivalent( + request, makeEppHttpRequestWithCertificate(requestContent, cookie1, cookie2)); // Nothing further to pass to the next handler. assertThat((Object) channel.readInbound()).isNull(); assertThat((Object) channel.readOutbound()).isNull(); @@ -317,13 +357,16 @@ class EppServiceHandlerTest { // First request written. channel.writeInbound(Unpooled.wrappedBuffer(requestContent1.getBytes(UTF_8))); FullHttpRequest request1 = channel.readInbound(); - assertHttpRequestEquivalent(request1, makeEppHttpRequest(requestContent1, cookie1, cookie2)); + assertHttpRequestEquivalent( + request1, makeEppHttpRequestWithCertificate(requestContent1, cookie1, cookie2)); String responseContent2 = "response2"; Cookie cookie3 = new DefaultCookie("name3", "value3"); Cookie newCookie2 = new DefaultCookie("name2", "newValue"); // Second response written. - channel.writeOutbound( - makeEppHttpResponse(responseContent2, HttpResponseStatus.OK, cookie3, newCookie2)); + HttpResponse response = + makeEppHttpResponse(responseContent2, HttpResponseStatus.OK, cookie3, newCookie2); + response.headers().set("Logged-In", "true"); + channel.writeOutbound(response); channel.readOutbound(); String requestContent2 = "request2"; // Second request written.