Configure and instantiate a Noise-over-WebSocket tunnel

This commit is contained in:
Jon Chambers
2024-05-20 18:59:47 -04:00
committed by Jon Chambers
parent e096c608ee
commit c5c5f642e8
6 changed files with 84 additions and 1 deletions

View File

@@ -28,7 +28,11 @@ import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.grpc.MetricCollectingServerInterceptor;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import io.netty.channel.local.LocalAddress;
import java.io.FileInputStream;
import java.net.http.HttpClient;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
@@ -46,6 +50,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
@@ -144,6 +149,8 @@ import org.whispersystems.textsecuregcm.grpc.RequestAttributesInterceptor;
import org.whispersystems.textsecuregcm.grpc.net.ClientConnectionManager;
import org.whispersystems.textsecuregcm.grpc.net.ManagedDefaultEventLoopGroup;
import org.whispersystems.textsecuregcm.grpc.net.ManagedLocalGrpcServer;
import org.whispersystems.textsecuregcm.grpc.net.ManagedNioEventLoopGroup;
import org.whispersystems.textsecuregcm.grpc.net.NoiseWebSocketTunnelServer;
import org.whispersystems.textsecuregcm.jetty.JettyHttpConfigurationCustomizer;
import org.whispersystems.textsecuregcm.limits.CardinalityEstimator;
import org.whispersystems.textsecuregcm.limits.PushChallengeManager;
@@ -829,9 +836,56 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
}
};
@Nullable final X509Certificate noiseWebSocketTlsCertificate;
@Nullable final PrivateKey noiseWebSocketTlsPrivateKey;
if (config.getNoiseWebSocketTunnelConfiguration().tlsKeyStoreFile() != null &&
config.getNoiseWebSocketTunnelConfiguration().tlsKeyStoreEntryAlias() != null &&
config.getNoiseWebSocketTunnelConfiguration().tlsKeyStorePassword() != null) {
try (final FileInputStream websocketNoiseTunnelTlsKeyStoreInputStream = new FileInputStream(config.getNoiseWebSocketTunnelConfiguration().tlsKeyStoreFile())) {
final KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(websocketNoiseTunnelTlsKeyStoreInputStream, config.getNoiseWebSocketTunnelConfiguration().tlsKeyStorePassword().value().toCharArray());
final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(config.getNoiseWebSocketTunnelConfiguration().tlsKeyStoreEntryAlias(),
new KeyStore.PasswordProtection(config.getNoiseWebSocketTunnelConfiguration().tlsKeyStorePassword().value().toCharArray()));
noiseWebSocketTlsCertificate = (X509Certificate) privateKeyEntry.getCertificate();
noiseWebSocketTlsPrivateKey = privateKeyEntry.getPrivateKey();
}
} else {
noiseWebSocketTlsCertificate = null;
noiseWebSocketTlsPrivateKey = null;
}
final ExecutorService noiseWebSocketDelegatedTaskExecutor = environment.lifecycle()
.executorService(name(getClass(), "noiseWebsocketDelegatedTask-%d"))
.minThreads(8)
.maxThreads(8)
.allowCoreThreadTimeOut(false)
.build();
final ManagedNioEventLoopGroup noiseWebSocketEventLoopGroup = new ManagedNioEventLoopGroup();
final NoiseWebSocketTunnelServer noiseWebSocketTunnelServer = new NoiseWebSocketTunnelServer(
config.getNoiseWebSocketTunnelConfiguration().port(),
new X509Certificate[] { noiseWebSocketTlsCertificate },
noiseWebSocketTlsPrivateKey,
noiseWebSocketEventLoopGroup,
noiseWebSocketDelegatedTaskExecutor,
clientConnectionManager,
clientPublicKeysManager,
config.getNoiseWebSocketTunnelConfiguration().noiseStaticKeyPair(),
config.getNoiseWebSocketTunnelConfiguration().noiseRootPublicKeySignature(),
authenticatedGrpcServerAddress,
anonymousGrpcServerAddress,
config.getNoiseWebSocketTunnelConfiguration().recognizedProxySecret().value());
environment.lifecycle().manage(localEventLoopGroup);
environment.lifecycle().manage(anonymousGrpcServer);
environment.lifecycle().manage(authenticatedGrpcServer);
environment.lifecycle().manage(noiseWebSocketEventLoopGroup);
environment.lifecycle().manage(noiseWebSocketTunnelServer);
final List<Filter> filters = new ArrayList<>();
filters.add(remoteDeprecationFilter);

View File

@@ -1,8 +1,26 @@
package org.whispersystems.textsecuregcm.configuration;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record NoiseWebSocketTunnelConfiguration(@Positive int port, @NotNull SecretString recognizedProxySecret) {
public record NoiseWebSocketTunnelConfiguration(@Positive int port,
@Nullable String tlsKeyStoreFile,
@Nullable String tlsKeyStoreEntryAlias,
@Nullable SecretString tlsKeyStorePassword,
@NotNull SecretBytes noiseStaticPrivateKey,
@NotNull byte[] noiseRootPublicKeySignature,
@NotNull SecretString recognizedProxySecret) {
public ECKeyPair noiseStaticKeyPair() throws InvalidKeyException {
final ECPrivateKey privateKey = Curve.decodePrivatePoint(noiseStaticPrivateKey().value());
return new ECKeyPair(privateKey.publicKey(), privateKey);
}
}