s2a: Address minor comments on PR#11113 (#11540)

* Use StandardCharsets in FakeS2AServerTest.
* Use add instead of offer in S2AStub.
* remove dead code in ProtoUtil.java.
* Mark convertTlsProtocolVersion as VisibleForTesting.
* S2AStub doesn't return responses at front of queue.
* Remove global SHARED_RESOURCE_CHANNELS.
* Don't suppress RethrowReflectiveOperationExceptionAsLinkageError.
* Update javadoc.
* Make clear which certs are used in tests + add how to regenerate.
This commit is contained in:
Riya Mehta 2024-09-27 08:47:56 -07:00 committed by GitHub
parent 9faa0f4eb0
commit fa18fec36e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 345 additions and 285 deletions

View File

@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import io.grpc.CallOptions; import io.grpc.CallOptions;
import io.grpc.Channel; import io.grpc.Channel;
import io.grpc.ChannelCredentials; import io.grpc.ChannelCredentials;
@ -30,16 +29,15 @@ import io.grpc.MethodDescriptor;
import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.internal.SharedResourceHolder.Resource;
import io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.NettyChannelBuilder;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
/** /**
* Provides APIs for managing gRPC channels to S2A servers. Each channel is local and plaintext. If * Provides APIs for managing gRPC channels to an S2A server. Each channel is local and plaintext.
* credentials are provided, they are used to secure the channel. * If credentials are provided, they are used to secure the channel.
* *
* <p>This is done as follows: for each S2A server, provides an implementation of gRPC's {@link * <p>This is done as follows: for an S2A server, provides an implementation of gRPC's {@link
* SharedResourceHolder.Resource} interface called a {@code Resource<Channel>}. A {@code * SharedResourceHolder.Resource} interface called a {@code Resource<Channel>}. A {@code
* Resource<Channel>} is a factory for creating gRPC channels to the S2A server at a given address, * Resource<Channel>} is a factory for creating gRPC channels to the S2A server at a given address,
* and a channel must be returned to the {@code Resource<Channel>} when it is no longer needed. * and a channel must be returned to the {@code Resource<Channel>} when it is no longer needed.
@ -56,8 +54,6 @@ import javax.annotation.concurrent.ThreadSafe;
*/ */
@ThreadSafe @ThreadSafe
public final class S2AHandshakerServiceChannel { public final class S2AHandshakerServiceChannel {
private static final ConcurrentMap<String, Resource<Channel>> SHARED_RESOURCE_CHANNELS =
Maps.newConcurrentMap();
private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10);
/** /**
@ -72,9 +68,7 @@ public final class S2AHandshakerServiceChannel {
public static Resource<Channel> getChannelResource( public static Resource<Channel> getChannelResource(
String s2aAddress, ChannelCredentials s2aChannelCredentials) { String s2aAddress, ChannelCredentials s2aChannelCredentials) {
checkNotNull(s2aAddress); checkNotNull(s2aAddress);
checkNotNull(s2aChannelCredentials); return new ChannelResource(s2aAddress, s2aChannelCredentials);
return SHARED_RESOURCE_CHANNELS.computeIfAbsent(
s2aAddress, channelResource -> new ChannelResource(s2aAddress, s2aChannelCredentials));
} }
/** /**

View File

@ -16,36 +16,11 @@
package io.grpc.s2a.handshaker; package io.grpc.s2a.handshaker;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
/** Converts proto messages to Netty strings. */ /** Converts proto messages to Netty strings. */
final class ProtoUtil { final class ProtoUtil {
/**
* Converts {@link Ciphersuite} to its {@link String} representation.
*
* @param ciphersuite the {@link Ciphersuite} to be converted.
* @return a {@link String} representing the ciphersuite.
* @throws AssertionError if the {@link Ciphersuite} is not one of the supported ciphersuites.
*/
static String convertCiphersuite(Ciphersuite ciphersuite) {
switch (ciphersuite) {
case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
case CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256";
case CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
case CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
case CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
default:
throw new AssertionError(
String.format("Ciphersuite %d is not supported.", ciphersuite.getNumber()));
}
}
/** /**
* Converts a {@link TLSVersion} object to its {@link String} representation. * Converts a {@link TLSVersion} object to its {@link String} representation.
@ -54,6 +29,7 @@ final class ProtoUtil {
* @return a {@link String} representation of the TLS version. * @return a {@link String} representation of the TLS version.
* @throws AssertionError if the {@code tlsVersion} is not one of the supported TLS versions. * @throws AssertionError if the {@code tlsVersion} is not one of the supported TLS versions.
*/ */
@VisibleForTesting
static String convertTlsProtocolVersion(TLSVersion tlsVersion) { static String convertTlsProtocolVersion(TLSVersion tlsVersion) {
switch (tlsVersion) { switch (tlsVersion) {
case TLS_VERSION_1_3: case TLS_VERSION_1_3:

View File

@ -84,6 +84,7 @@ class S2AStub implements AutoCloseable {
* @throws IOException if an unexpected response is received, or if the {@code reader} or {@code * @throws IOException if an unexpected response is received, or if the {@code reader} or {@code
* writer} calls their {@code onError} method. * writer} calls their {@code onError} method.
*/ */
@SuppressWarnings("CheckReturnValue")
public SessionResp send(SessionReq req) throws IOException, InterruptedException { public SessionResp send(SessionReq req) throws IOException, InterruptedException {
if (doneWriting && doneReading) { if (doneWriting && doneReading) {
logger.log(Level.INFO, "Stream to the S2A is closed."); logger.log(Level.INFO, "Stream to the S2A is closed.");
@ -92,9 +93,8 @@ class S2AStub implements AutoCloseable {
createWriterIfNull(); createWriterIfNull();
if (!responses.isEmpty()) { if (!responses.isEmpty()) {
IOException exception = null; IOException exception = null;
SessionResp resp = null;
try { try {
resp = responses.take().getResultOrThrow(); responses.take().getResultOrThrow();
} catch (IOException e) { } catch (IOException e) {
exception = e; exception = e;
} }
@ -104,14 +104,15 @@ class S2AStub implements AutoCloseable {
"Received an unexpected response from a host at the S2A's address. The S2A might be" "Received an unexpected response from a host at the S2A's address. The S2A might be"
+ " unavailable." + " unavailable."
+ exception.getMessage()); + exception.getMessage());
} else {
throw new IOException("Received an unexpected response from a host at the S2A's address.");
} }
return resp;
} }
try { try {
writer.onNext(req); writer.onNext(req);
} catch (RuntimeException e) { } catch (RuntimeException e) {
writer.onError(e); writer.onError(e);
responses.offer(Result.createWithThrowable(e)); responses.add(Result.createWithThrowable(e));
} }
try { try {
return responses.take().getResultOrThrow(); return responses.take().getResultOrThrow();
@ -159,7 +160,7 @@ class S2AStub implements AutoCloseable {
@Override @Override
public void onNext(SessionResp resp) { public void onNext(SessionResp resp) {
verify(!doneReading); verify(!doneReading);
responses.offer(Result.createWithResponse(resp)); responses.add(Result.createWithResponse(resp));
} }
/** /**
@ -169,7 +170,7 @@ class S2AStub implements AutoCloseable {
*/ */
@Override @Override
public void onError(Throwable t) { public void onError(Throwable t) {
responses.offer(Result.createWithThrowable(t)); responses.add(Result.createWithThrowable(t));
} }
/** /**
@ -180,7 +181,7 @@ class S2AStub implements AutoCloseable {
public void onCompleted() { public void onCompleted() {
logger.log(Level.INFO, "Reading from the S2A is complete."); logger.log(Level.INFO, "Reading from the S2A is complete.");
doneReading = true; doneReading = true;
responses.offer( responses.add(
Result.createWithThrowable( Result.createWithThrowable(
new ConnectionClosedException("Reading from the S2A is complete."))); new ConnectionClosedException("Reading from the S2A is complete.")));
} }

View File

@ -27,7 +27,6 @@ public final class AccessTokenManager {
private final TokenFetcher tokenFetcher; private final TokenFetcher tokenFetcher;
/** Creates an {@code AccessTokenManager} based on the environment where the application runs. */ /** Creates an {@code AccessTokenManager} based on the environment where the application runs. */
@SuppressWarnings("RethrowReflectiveOperationExceptionAsLinkageError")
public static Optional<AccessTokenManager> create() { public static Optional<AccessTokenManager> create() {
Optional<?> tokenFetcher; Optional<?> tokenFetcher;
try { try {
@ -38,7 +37,7 @@ public final class AccessTokenManager {
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
tokenFetcher = Optional.empty(); tokenFetcher = Optional.empty();
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new AssertionError(e); throw new LinkageError(e.getMessage(), e);
} }
return tokenFetcher.isPresent() return tokenFetcher.isPresent()
? Optional.of(new AccessTokenManager((TokenFetcher) tokenFetcher.get())) ? Optional.of(new AccessTokenManager((TokenFetcher) tokenFetcher.get()))

View File

@ -89,10 +89,10 @@ public final class S2AHandshakerServiceChannelTest {
/** /**
* Creates two {@code Resoure<Channel>}s for the same target address and verifies that they are * Creates two {@code Resoure<Channel>}s for the same target address and verifies that they are
* equal. * distinct.
*/ */
@Test @Test
public void getChannelResource_twoEqualChannels() { public void getChannelResource_twoUnEqualChannels() {
Resource<Channel> resource = Resource<Channel> resource =
S2AHandshakerServiceChannel.getChannelResource( S2AHandshakerServiceChannel.getChannelResource(
"localhost:" + plaintextServer.getPort(), "localhost:" + plaintextServer.getPort(),
@ -101,19 +101,19 @@ public final class S2AHandshakerServiceChannelTest {
S2AHandshakerServiceChannel.getChannelResource( S2AHandshakerServiceChannel.getChannelResource(
"localhost:" + plaintextServer.getPort(), "localhost:" + plaintextServer.getPort(),
InsecureChannelCredentials.create()); InsecureChannelCredentials.create());
assertThat(resource).isEqualTo(resourceTwo); assertThat(resource).isNotEqualTo(resourceTwo);
} }
/** Same as getChannelResource_twoEqualChannels, but use mTLS. */ /** Same as getChannelResource_twoUnEqualChannels, but use mTLS. */
@Test @Test
public void getChannelResource_mtlsTwoEqualChannels() throws Exception { public void getChannelResource_mtlsTwoUnEqualChannels() throws Exception {
Resource<Channel> resource = Resource<Channel> resource =
S2AHandshakerServiceChannel.getChannelResource( S2AHandshakerServiceChannel.getChannelResource(
"localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials());
Resource<Channel> resourceTwo = Resource<Channel> resourceTwo =
S2AHandshakerServiceChannel.getChannelResource( S2AHandshakerServiceChannel.getChannelResource(
"localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials());
assertThat(resource).isEqualTo(resourceTwo); assertThat(resource).isNotEqualTo(resourceTwo);
} }
/** /**

View File

@ -17,6 +17,7 @@
package io.grpc.s2a.handshaker; package io.grpc.s2a.handshaker;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -38,7 +39,11 @@ public final class FakeS2AServer extends S2AServiceGrpc.S2AServiceImplBase {
@Override @Override
public void onNext(SessionReq req) { public void onNext(SessionReq req) {
logger.info("Received a request from client."); logger.info("Received a request from client.");
try {
responseObserver.onNext(writer.handleResponse(req)); responseObserver.onNext(writer.handleResponse(req));
} catch (IOException e) {
responseObserver.onError(e);
}
} }
@Override @Override

View File

@ -29,6 +29,9 @@ import io.grpc.ServerBuilder;
import io.grpc.benchmarks.Utils; import io.grpc.benchmarks.Utils;
import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -45,9 +48,7 @@ public final class FakeS2AServerTest {
private static final Logger logger = Logger.getLogger(FakeS2AServerTest.class.getName()); private static final Logger logger = Logger.getLogger(FakeS2AServerTest.class.getName());
private static final ImmutableList<ByteString> FAKE_CERT_DER_CHAIN = private static final ImmutableList<ByteString> FAKE_CERT_DER_CHAIN =
ImmutableList.of( ImmutableList.of(ByteString.copyFrom("fake-der-chain".getBytes(StandardCharsets.US_ASCII)));
ByteString.copyFrom(
new byte[] {'f', 'a', 'k', 'e', '-', 'd', 'e', 'r', '-', 'c', 'h', 'a', 'i', 'n'}));
private int port; private int port;
private String serverAddress; private String serverAddress;
private SessionResp response = null; private SessionResp response = null;
@ -68,7 +69,7 @@ public final class FakeS2AServerTest {
@Test @Test
public void callS2AServerOnce_getTlsConfiguration_returnsValidResult() public void callS2AServerOnce_getTlsConfiguration_returnsValidResult()
throws InterruptedException { throws InterruptedException, IOException {
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
logger.info("Client connecting to: " + serverAddress); logger.info("Client connecting to: " + serverAddress);
ManagedChannel channel = ManagedChannel channel =
@ -122,9 +123,12 @@ public final class FakeS2AServerTest {
GetTlsConfigurationResp.newBuilder() GetTlsConfigurationResp.newBuilder()
.setClientTlsConfiguration( .setClientTlsConfiguration(
GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder()
.addCertificateChain(FakeWriter.LEAF_CERT) .addCertificateChain(new String(Files.readAllBytes(
.addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) .addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8))
.setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3)
.setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3)
.addCiphersuites( .addCiphersuites(

View File

@ -23,7 +23,10 @@ import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -50,59 +53,18 @@ final class FakeWriter implements StreamObserver<SessionReq> {
FAILURE FAILURE
} }
public static final String LEAF_CERT = public static final File leafCertFile =
"-----BEGIN CERTIFICATE-----\n" new File("src/test/resources/leaf_cert_ec.pem");
+ "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" public static final File cert2File =
+ "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" new File("src/test/resources/int_cert2_ec.pem");
+ "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" public static final File cert1File =
+ "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" new File("src/test/resources/int_cert1_ec.pem");
+ "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n"
+ "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n"
+ "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n"
+ "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n"
+ "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n"
+ "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n"
+ "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n"
+ "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n"
+ "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n"
+ "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n"
+ "-----END CERTIFICATE-----";
public static final String INTERMEDIATE_CERT_2 =
"-----BEGIN CERTIFICATE-----\n"
+ "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n"
+ "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n"
+ "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n"
+ "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n"
+ "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n"
+ "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n"
+ "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n"
+ "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n"
+ "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n"
+ "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n"
+ "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n"
+ "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n"
+ "gjIY71MO\n"
+ "-----END CERTIFICATE-----";
public static final String INTERMEDIATE_CERT_1 =
"-----BEGIN CERTIFICATE-----\n"
+ "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n"
+ "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n"
+ "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n"
+ "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n"
+ "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n"
+ "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n"
+ "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n"
+ "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n"
+ "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n"
+ "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n"
+ "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n"
+ "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n"
+ "-----END CERTIFICATE-----";
// src/test/resources/leaf_key_ec.pem
private static final String PRIVATE_KEY = private static final String PRIVATE_KEY =
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf" "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2HBqtWTWu4NLiow"
+ "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t" + "ar8vh+9vAmCONE59C+jXNAb9r8ehRANCAATRM8ozcr8PTOVsZNWh+rTmJ6t+rODu"
+ "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id"; + "g3LwWpUQq9h7AddjGlLrrTNrceOyO7nh9aEk5plKhs/h7PO8+vkEFsEx";
private static final ImmutableMap<SignatureAlgorithm, String> private static final ImmutableMap<SignatureAlgorithm, String>
ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER = ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER =
ImmutableMap.of( ImmutableMap.of(
@ -167,15 +129,19 @@ final class FakeWriter implements StreamObserver<SessionReq> {
} }
void sendGetTlsConfigResp() { void sendGetTlsConfigResp() {
try {
reader.onNext( reader.onNext(
SessionResp.newBuilder() SessionResp.newBuilder()
.setGetTlsConfigurationResp( .setGetTlsConfigurationResp(
GetTlsConfigurationResp.newBuilder() GetTlsConfigurationResp.newBuilder()
.setClientTlsConfiguration( .setClientTlsConfiguration(
GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder()
.addCertificateChain(LEAF_CERT) .addCertificateChain(new String(Files.readAllBytes(
.addCertificateChain(INTERMEDIATE_CERT_2) FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(INTERMEDIATE_CERT_1) .addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8))
.setMinTlsVersion(TLS_VERSION_1_3) .setMinTlsVersion(TLS_VERSION_1_3)
.setMaxTlsVersion(TLS_VERSION_1_3) .setMaxTlsVersion(TLS_VERSION_1_3)
.addCiphersuites( .addCiphersuites(
@ -183,8 +149,12 @@ final class FakeWriter implements StreamObserver<SessionReq> {
.addCiphersuites( .addCiphersuites(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
.addCiphersuites( .addCiphersuites(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) Ciphersuite
.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)))
.build()); .build());
} catch (IOException e) {
reader.onError(e);
}
} }
boolean isFakeWriterClosed() { boolean isFakeWriterClosed() {
@ -195,7 +165,11 @@ final class FakeWriter implements StreamObserver<SessionReq> {
public void onNext(SessionReq sessionReq) { public void onNext(SessionReq sessionReq) {
switch (behavior) { switch (behavior) {
case OK_STATUS: case OK_STATUS:
try {
reader.onNext(handleResponse(sessionReq)); reader.onNext(handleResponse(sessionReq));
} catch (IOException e) {
reader.onError(e);
}
break; break;
case EMPTY_RESPONSE: case EMPTY_RESPONSE:
reader.onNext(SessionResp.getDefaultInstance()); reader.onNext(SessionResp.getDefaultInstance());
@ -216,25 +190,36 @@ final class FakeWriter implements StreamObserver<SessionReq> {
reader.onCompleted(); reader.onCompleted();
break; break;
case BAD_TLS_VERSION_RESPONSE: case BAD_TLS_VERSION_RESPONSE:
try {
reader.onNext( reader.onNext(
SessionResp.newBuilder() SessionResp.newBuilder()
.setGetTlsConfigurationResp( .setGetTlsConfigurationResp(
GetTlsConfigurationResp.newBuilder() GetTlsConfigurationResp.newBuilder()
.setClientTlsConfiguration( .setClientTlsConfiguration(
GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder()
.addCertificateChain(LEAF_CERT) .addCertificateChain(new String(Files.readAllBytes(
.addCertificateChain(INTERMEDIATE_CERT_2) FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(INTERMEDIATE_CERT_1) .addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8))
.setMinTlsVersion(TLS_VERSION_1_3) .setMinTlsVersion(TLS_VERSION_1_3)
.setMaxTlsVersion(TLS_VERSION_1_2))) .setMaxTlsVersion(TLS_VERSION_1_2)))
.build()); .build());
} catch (IOException e) {
reader.onError(e);
}
break; break;
default: default:
try {
reader.onNext(handleResponse(sessionReq)); reader.onNext(handleResponse(sessionReq));
} catch (IOException e) {
reader.onError(e);
}
} }
} }
SessionResp handleResponse(SessionReq sessionReq) { SessionResp handleResponse(SessionReq sessionReq) throws IOException {
if (sessionReq.hasGetTlsConfigurationReq()) { if (sessionReq.hasGetTlsConfigurationReq()) {
return handleGetTlsConfigurationReq(sessionReq.getGetTlsConfigurationReq()); return handleGetTlsConfigurationReq(sessionReq.getGetTlsConfigurationReq());
} }
@ -253,7 +238,8 @@ final class FakeWriter implements StreamObserver<SessionReq> {
.build(); .build();
} }
private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) { private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req)
throws IOException {
if (!req.getConnectionSide().equals(ConnectionSide.CONNECTION_SIDE_CLIENT)) { if (!req.getConnectionSide().equals(ConnectionSide.CONNECTION_SIDE_CLIENT)) {
return SessionResp.newBuilder() return SessionResp.newBuilder()
.setStatus( .setStatus(
@ -267,9 +253,12 @@ final class FakeWriter implements StreamObserver<SessionReq> {
GetTlsConfigurationResp.newBuilder() GetTlsConfigurationResp.newBuilder()
.setClientTlsConfiguration( .setClientTlsConfiguration(
GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder()
.addCertificateChain(LEAF_CERT) .addCertificateChain(new String(Files.readAllBytes(
.addCertificateChain(INTERMEDIATE_CERT_2) FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(INTERMEDIATE_CERT_1) .addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8))
.setMinTlsVersion(TLS_VERSION_1_3) .setMinTlsVersion(TLS_VERSION_1_3)
.setMaxTlsVersion(TLS_VERSION_1_3) .setMaxTlsVersion(TLS_VERSION_1_3)
.addCiphersuites( .addCiphersuites(

View File

@ -17,7 +17,6 @@
package io.grpc.s2a.handshaker; package io.grpc.s2a.handshaker;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import io.grpc.ChannelCredentials; import io.grpc.ChannelCredentials;
@ -42,7 +41,6 @@ import io.netty.handler.ssl.OpenSslSessionContext;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -58,72 +56,12 @@ import org.junit.runners.JUnit4;
public final class IntegrationTest { public final class IntegrationTest {
private static final Logger logger = Logger.getLogger(FakeS2AServer.class.getName()); private static final Logger logger = Logger.getLogger(FakeS2AServer.class.getName());
private static final String CERT_CHAIN = public static final File privateKeyFile =
"-----BEGIN CERTIFICATE-----\n" new File("src/test/resources/leaf_key_ec.pem");
+ "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" public static final File rootCertFile =
+ "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" new File("src/test/resources/root_cert_ec.pem");
+ "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" public static final File certChainFile =
+ "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" new File("src/test/resources/cert_chain_ec.pem");
+ "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n"
+ "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n"
+ "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n"
+ "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n"
+ "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n"
+ "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n"
+ "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n"
+ "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n"
+ "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n"
+ "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n"
+ "-----END CERTIFICATE-----\n"
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n"
+ "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n"
+ "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n"
+ "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n"
+ "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n"
+ "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n"
+ "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n"
+ "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n"
+ "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n"
+ "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n"
+ "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n"
+ "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n"
+ "gjIY71MO\n"
+ "-----END CERTIFICATE-----\n"
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n"
+ "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n"
+ "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n"
+ "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n"
+ "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n"
+ "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n"
+ "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n"
+ "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n"
+ "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n"
+ "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n"
+ "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n"
+ "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n"
+ "-----END CERTIFICATE-----";
private static final String ROOT_PEM =
"-----BEGIN CERTIFICATE-----\n"
+ "MIIBtTCCAVqgAwIBAgIUbAe+8OocndQXRBCElLBxBSdfdV8wCgYIKoZIzj0EAwIw\n"
+ "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n"
+ "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDcxFzAV\n"
+ "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMQ0wCwYDVQQLDARyb290MQ0wCwYDVQQDDAQx\n"
+ "MjM0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaMY2tBW5r1t0+vhayz0ZoGMF\n"
+ "boX/ZmmCmIh0iTWg4madvwNOh74CMVVvDUlXZcuVqZ3vVIX/a7PTFVqUwQlKW6NC\n"
+ "MEAwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMX+\n"
+ "vebuj/lYfYEC23IA8HoIW0HsMAoGCCqGSM49BAMCA0kAMEYCIQDETd27nsUTXKWY\n"
+ "CiOno78O09gK95NoTkPU5e2chJYMqAIhALYFAyh7PU5xgFQsN9hiqgsHUc5/pmBG\n"
+ "BGjJ1iz8rWGJ\n"
+ "-----END CERTIFICATE-----";
private static final String PRIVATE_KEY =
"-----BEGIN PRIVATE KEY-----\n"
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf\n"
+ "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t\n"
+ "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id\n"
+ "-----END PRIVATE KEY-----";
private String s2aAddress; private String s2aAddress;
private Server s2aServer; private Server s2aServer;
private String s2aDelayAddress; private String s2aDelayAddress;
@ -252,13 +190,11 @@ public final class IntegrationTest {
private static SslContext buildSslContext() throws SSLException { private static SslContext buildSslContext() throws SSLException {
SslContextBuilder sslServerContextBuilder = SslContextBuilder sslServerContextBuilder =
SslContextBuilder.forServer( SslContextBuilder.forServer(certChainFile, privateKeyFile);
new ByteArrayInputStream(CERT_CHAIN.getBytes(UTF_8)),
new ByteArrayInputStream(PRIVATE_KEY.getBytes(UTF_8)));
SslContext sslServerContext = SslContext sslServerContext =
GrpcSslContexts.configure(sslServerContextBuilder, SslProvider.OPENSSL) GrpcSslContexts.configure(sslServerContextBuilder, SslProvider.OPENSSL)
.protocols("TLSv1.3", "TLSv1.2") .protocols("TLSv1.3", "TLSv1.2")
.trustManager(new ByteArrayInputStream(ROOT_PEM.getBytes(UTF_8))) .trustManager(rootCertFile)
.clientAuth(ClientAuth.REQUIRE) .clientAuth(ClientAuth.REQUIRE)
.build(); .build();

View File

@ -30,47 +30,6 @@ import org.junit.runners.JUnit4;
public final class ProtoUtilTest { public final class ProtoUtilTest {
@Rule public final Expect expect = Expect.create(); @Rule public final Expect expect = Expect.create();
@Test
public void convertCiphersuite_success() {
expect
.that(
ProtoUtil.convertCiphersuite(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256))
.isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
expect
.that(
ProtoUtil.convertCiphersuite(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384))
.isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
expect
.that(
ProtoUtil.convertCiphersuite(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))
.isEqualTo("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256");
expect
.that(
ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256))
.isEqualTo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
expect
.that(
ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384))
.isEqualTo("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
expect
.that(
ProtoUtil.convertCiphersuite(
Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256))
.isEqualTo("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256");
}
@Test
public void convertCiphersuite_withUnspecifiedCiphersuite_fails() {
AssertionError expected =
assertThrows(
AssertionError.class,
() -> ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_UNSPECIFIED));
expect.that(expected).hasMessageThat().isEqualTo("Ciphersuite 0 is not supported.");
}
@Test @Test
public void convertTlsProtocolVersion_success() { public void convertTlsProtocolVersion_success() {
expect expect

View File

@ -30,6 +30,8 @@ import io.grpc.s2a.handshaker.S2AIdentity;
import io.netty.handler.ssl.OpenSslPrivateKeyMethod; import io.netty.handler.ssl.OpenSslPrivateKeyMethod;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
@ -61,7 +63,8 @@ public final class S2APrivateKeyMethodTest {
private static boolean verifySignature( private static boolean verifySignature(
byte[] dataToSign, byte[] signature, String signatureAlgorithm) throws Exception { byte[] dataToSign, byte[] signature, String signatureAlgorithm) throws Exception {
Signature sig = Signature.getInstance(signatureAlgorithm); Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initVerify(extractPublicKeyFromPem(FakeWriter.LEAF_CERT)); sig.initVerify(extractPublicKeyFromPem(new String(
Files.readAllBytes(FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)));
sig.update(dataToSign); sig.update(dataToSign);
return sig.verify(signature); return sig.verify(signature);
} }

View File

@ -50,6 +50,7 @@ import io.netty.handler.codec.http2.Http2ConnectionDecoder;
import io.netty.handler.codec.http2.Http2ConnectionEncoder; import io.netty.handler.codec.http2.Http2ConnectionEncoder;
import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Settings;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -246,7 +247,11 @@ public class S2AProtocolNegotiatorFactoryTest {
return new StreamObserver<SessionReq>() { return new StreamObserver<SessionReq>() {
@Override @Override
public void onNext(SessionReq req) { public void onNext(SessionReq req) {
try {
responseObserver.onNext(writer.handleResponse(req)); responseObserver.onNext(writer.handleResponse(req));
} catch (IOException e) {
responseObserver.onError(e);
}
} }
@Override @Override

View File

@ -28,6 +28,8 @@ import io.grpc.s2a.channel.S2AGrpcChannelPool;
import io.grpc.s2a.channel.S2AHandshakerServiceChannel; import io.grpc.s2a.channel.S2AHandshakerServiceChannel;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -82,9 +84,12 @@ public class S2AStubTest {
GetTlsConfigurationResp.newBuilder() GetTlsConfigurationResp.newBuilder()
.setClientTlsConfiguration( .setClientTlsConfiguration(
GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder()
.addCertificateChain(FakeWriter.LEAF_CERT) .addCertificateChain(new String(Files.readAllBytes(
.addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) .addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8))
.addCertificateChain(new String(Files.readAllBytes(
FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8))
.setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3)
.setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3)
.addCiphersuites( .addCiphersuites(
@ -189,26 +194,13 @@ public class S2AStubTest {
@Test @Test
public void send_receiveDelayedResponse() throws Exception { public void send_receiveDelayedResponse() throws Exception {
writer.sendGetTlsConfigResp(); writer.sendGetTlsConfigResp();
SessionResp resp = stub.send(SessionReq.getDefaultInstance()); IOException expectedException =
SessionResp expected = assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance()));
SessionResp.newBuilder() assertThat(expectedException)
.setGetTlsConfigurationResp( .hasMessageThat()
GetTlsConfigurationResp.newBuilder() .contains("Received an unexpected response from a host at the S2A's address.");
.setClientTlsConfiguration(
GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() assertThat(stub.getResponses()).isEmpty();
.addCertificateChain(FakeWriter.LEAF_CERT)
.addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2)
.addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1)
.setMinTlsVersion(TLSVersion.TLS_VERSION_1_3)
.setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3)
.addCiphersuites(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
.addCiphersuites(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
.addCiphersuites(
Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)))
.build();
assertThat(resp).ignoringRepeatedFieldOrder().isEqualTo(expected);
} }
@Test @Test

View File

@ -30,3 +30,40 @@ Sign CSRs for server and client
openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in server.csr -out server_cert.pem -days 7305 -extfile config.cnf -extensions req_ext openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in server.csr -out server_cert.pem -days 7305 -extfile config.cnf -extensions req_ext
openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305 openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305
``` ```
Generate self-signed ECDSA root cert
```
openssl ecparam -name prime256v1 -genkey -noout -out temp.pem
openssl pkcs8 -topk8 -in temp.pem -out root_key_ec.pem -nocrypt
rm temp.pem
openssl req -x509 -days 7305 -new -key root_key_ec.pem -nodes -out root_cert_ec.pem -config root_ec.cnf -extensions 'v3_req'
```
Generate a chain of ECDSA certs
```
openssl ecparam -name prime256v1 -genkey -noout -out temp.pem
openssl pkcs8 -topk8 -in temp.pem -out int_key2_ec.pem -nocrypt
rm temp.pem
openssl req -key int_key2_ec.pem -new -out temp.csr -config int_cert2.cnf
openssl x509 -req -days 7305 -in temp.csr -CA root_cert_ec.pem -CAkey root_key_ec.pem -CAcreateserial -out int_cert2_ec.pem -extfile int_cert2.cnf -extensions 'v3_req'
openssl ecparam -name prime256v1 -genkey -noout -out temp.pem
openssl pkcs8 -topk8 -in temp.pem -out int_key1_ec.pem -nocrypt
rm temp.pem
openssl req -key int_key1_ec.pem -new -out temp.csr -config int_cert1.cnf
openssl x509 -req -days 7305 -in temp.csr -CA int_cert2_ec.pem -CAkey int_key2_ec.pem -CAcreateserial -out int_cert1_ec.pem -extfile int_cert1.cnf -extensions 'v3_req'
openssl ecparam -name prime256v1 -genkey -noout -out temp.pem
openssl pkcs8 -topk8 -in temp.pem -out leaf_key_ec.pem -nocrypt
rm temp.pem
openssl req -key leaf_key_ec.pem -new -out temp.csr -config leaf.cnf
openssl x509 -req -days 7305 -in temp.csr -CA int_cert1_ec.pem -CAkey int_key1_ec.pem -CAcreateserial -out leaf_cert_ec.pem -extfile leaf.cnf -extensions 'v3_req'
```
```
cat leaf_cert_ec.pem int_cert1_ec.pem int_cert2_ec.pem > cert_chain_ec.pem
```

View File

@ -0,0 +1,36 @@
-----BEGIN CERTIFICATE-----
MIIB0jCCAXigAwIBAgIUBV1dftEhhEMTI83L6jpeJn2tuyQwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIzMDQwMFoXDTQ0MDkxOTIzMDQwMFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0TPKM3K/
D0zlbGTVofq05ierfqzg7oNy8FqVEKvYewHXYxpS660za3Hjsju54fWhJOaZSobP
4ezzvPr5BBbBMaOBgzCBgDAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPvP7dnB
dg8ZoLB0w62tbvoIRRHPMB8GA1UdIwQYMBaAFHeH+MNh2fgyjNHYP9hLAv9Sl1yD
MAoGCCqGSM49BAMCA0gAMEUCIBcsImaxeFjxFXCXYNQJnde+rsEOgbeAHrAC0SZQ
NlB2AiEA4epDhw/o+6BfgDbqlZsNEHkScPrwupnBQGLQlmNJe2c=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB1zCCAX6gAwIBAgIUW4GYHncSLeb7Tmw7FMjX/DesReIwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIzMDA0MVoXDTQ0MDkxOTIzMDA0MVowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAqtg2E+h
Wfr5dnewqsCLwM0PohkB83Gh7V3i/TPFkNKF/V6pKdz5a3Z8sicG+g7uJX+eyOoD
43Z8woO7MgJl8aOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
FHeH+MNh2fgyjNHYP9hLAv9Sl1yDMB8GA1UdIwQYMBaAFP+PTOryxis9d7HVfqhP
MyMEgMZOMAoGCCqGSM49BAMCA0cAMEQCIHbzJvxHMIDPBRi1e047K0mqKKBSfViS
guiDSoQ5g5OuAiBT5ePqDLs4PyrK6XFkiEWoRX8Z5T9y419Go+fpLM+DaA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB1zCCAX6gAwIBAgIUBBKkTrqFxQUist2pK2uj8/DRnKMwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIyNTYwNloXDTQ0MDkxOTIyNTYwNlowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7/Te2z
jS8KlpF8RMMYaZtKf6EZlrZIIo5SO9j6baAKXVna9LmDCrzXnOLIeqOuZq0ODizU
i4DFALB2yd5BkaOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYE
FP+PTOryxis9d7HVfqhPMyMEgMZOMB8GA1UdIwQYMBaAFFITbB0BULPtynN9SMki
lEarWxcKMAoGCCqGSM49BAMCA0cAMEQCIHK4cTTx4Ti7Te9hA9VVtHoMCt5fL4Cl
XnQR6D5xW4pPAiAQ+CilQdZUhVK5bU6wbrwLgcwf+40ETK/KASId5970rQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,14 @@
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
O = o
OU = ou
CN = cn
[v3_req]
keyUsage = critical, keyCertSign, cRLSign
extendedKeyUsage = critical, clientAuth, serverAuth
basicConstraints = critical, CA:true, pathlen: 1

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB1zCCAX6gAwIBAgIUW4GYHncSLeb7Tmw7FMjX/DesReIwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIzMDA0MVoXDTQ0MDkxOTIzMDA0MVowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAqtg2E+h
Wfr5dnewqsCLwM0PohkB83Gh7V3i/TPFkNKF/V6pKdz5a3Z8sicG+g7uJX+eyOoD
43Z8woO7MgJl8aOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
FHeH+MNh2fgyjNHYP9hLAv9Sl1yDMB8GA1UdIwQYMBaAFP+PTOryxis9d7HVfqhP
MyMEgMZOMAoGCCqGSM49BAMCA0cAMEQCIHbzJvxHMIDPBRi1e047K0mqKKBSfViS
guiDSoQ5g5OuAiBT5ePqDLs4PyrK6XFkiEWoRX8Z5T9y419Go+fpLM+DaA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,14 @@
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
O = o
OU = ou
CN = cn
[v3_req]
keyUsage = critical, keyCertSign, cRLSign
extendedKeyUsage = critical, clientAuth, serverAuth
basicConstraints = critical, CA:true, pathlen: 2

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB1zCCAX6gAwIBAgIUBBKkTrqFxQUist2pK2uj8/DRnKMwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIyNTYwNloXDTQ0MDkxOTIyNTYwNlowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7/Te2z
jS8KlpF8RMMYaZtKf6EZlrZIIo5SO9j6baAKXVna9LmDCrzXnOLIeqOuZq0ODizU
i4DFALB2yd5BkaOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYE
FP+PTOryxis9d7HVfqhPMyMEgMZOMB8GA1UdIwQYMBaAFFITbB0BULPtynN9SMki
lEarWxcKMAoGCCqGSM49BAMCA0cAMEQCIHK4cTTx4Ti7Te9hA9VVtHoMCt5fL4Cl
XnQR6D5xW4pPAiAQ+CilQdZUhVK5bU6wbrwLgcwf+40ETK/KASId5970rQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgLIQUM1HkFM/LWND8
jCZ4wHXjFZ1ZZmQolahkZB0O1VChRANCAAQCq2DYT6FZ+vl2d7CqwIvAzQ+iGQHz
caHtXeL9M8WQ0oX9Xqkp3PlrdnyyJwb6Du4lf57I6gPjdnzCg7syAmXx
-----END PRIVATE KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGfm6kyaAMMrmYGhS
jxprBwtcZdP6qXlU1cVIO5bOT8qhRANCAAQVbv9N7bONLwqWkXxEwxhpm0p/oRmW
tkgijlI72PptoApdWdr0uYMKvNec4sh6o65mrQ4OLNSLgMUAsHbJ3kGR
-----END PRIVATE KEY-----

View File

@ -0,0 +1,14 @@
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
O = o
OU = ou
CN = cn
[v3_req]
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, clientAuth, serverAuth
basicConstraints = critical, CA:false

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB0jCCAXigAwIBAgIUBV1dftEhhEMTI83L6jpeJn2tuyQwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIzMDQwMFoXDTQ0MDkxOTIzMDQwMFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0TPKM3K/
D0zlbGTVofq05ierfqzg7oNy8FqVEKvYewHXYxpS660za3Hjsju54fWhJOaZSobP
4ezzvPr5BBbBMaOBgzCBgDAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYI
KwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPvP7dnB
dg8ZoLB0w62tbvoIRRHPMB8GA1UdIwQYMBaAFHeH+MNh2fgyjNHYP9hLAv9Sl1yD
MAoGCCqGSM49BAMCA0gAMEUCIBcsImaxeFjxFXCXYNQJnde+rsEOgbeAHrAC0SZQ
NlB2AiEA4epDhw/o+6BfgDbqlZsNEHkScPrwupnBQGLQlmNJe2c=
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2HBqtWTWu4NLiow
ar8vh+9vAmCONE59C+jXNAb9r8ehRANCAATRM8ozcr8PTOVsZNWh+rTmJ6t+rODu
g3LwWpUQq9h7AddjGlLrrTNrceOyO7nh9aEk5plKhs/h7PO8+vkEFsEx
-----END PRIVATE KEY-----

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBrzCCAVWgAwIBAgIUGV+9j5V61CZaa6mbrchDag5miEQwCgYIKoZIzj0EAwIw
JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx
OTIyNDMwOFoXDTQ0MDkxOTIyNDMwOFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC
b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDVPIq1ds
/MX52CX9YU1RdEeM89YP4o3BN8OiP2O4qcuc11k4Qu4Mo4RWeN9OJpNElTQJ0K8n
/rIvbmw8AIMquaNhMF8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUF
BwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRSE2wdAVCz
7cpzfUjJIpRGq1sXCjAKBggqhkjOPQQDAgNIADBFAiEA1TEfHWArDnepmtMDQ4wd
Q3uqPrV2Ye2KMO67/BHEGIQCIFu3JutXYYVU/CinwH89AJW+FJ7zokaPjCDkbiOH
+h+H
-----END CERTIFICATE-----

View File

@ -0,0 +1,14 @@
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
O = o
OU = ou
CN = cn
[v3_req]
keyUsage = critical, keyCertSign, cRLSign
extendedKeyUsage = serverAuth, clientAuth
basicConstraints = critical, CA:true

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd5oZmQBOtMF0xfc3
uRuw5EDhA1thJKKeHfrij9FMkfahRANCAAQNU8irV2z8xfnYJf1hTVF0R4zz1g/i
jcE3w6I/Y7ipy5zXWThC7gyjhFZ4304mk0SVNAnQryf+si9ubDwAgyq5
-----END PRIVATE KEY-----