Add client identity to key and certificate to KeyTransparencyServiceClient

This commit is contained in:
Chris Eager
2024-10-18 14:44:17 -05:00
committed by Chris Eager
parent 324913d2da
commit 1959ca2d96
7 changed files with 129 additions and 6 deletions

View File

@@ -609,6 +609,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getKeyTransparencyServiceConfiguration().host(),
config.getKeyTransparencyServiceConfiguration().port(),
config.getKeyTransparencyServiceConfiguration().tlsCertificate(),
config.getKeyTransparencyServiceConfiguration().clientCertificate(),
config.getKeyTransparencyServiceConfiguration().clientPrivateKey().value(),
keyTransparencyCallbackExecutor);
SecureValueRecovery2Client secureValueRecovery2Client = new SecureValueRecovery2Client(svr2CredentialsGenerator,
secureValueRecoveryServiceExecutor, secureValueRecoveryServiceRetryExecutor, config.getSvr2Configuration());

View File

@@ -5,9 +5,13 @@
package org.whispersystems.textsecuregcm.configuration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
public record KeyTransparencyServiceConfiguration(@NotBlank String host,
@Positive int port,
@NotBlank String tlsCertificate) {}
@NotBlank String tlsCertificate,
@NotBlank String clientCertificate,
@NotNull SecretString clientPrivateKey) {}

View File

@@ -7,27 +7,40 @@ import io.grpc.ChannelCredentials;
import io.grpc.Deadline;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import io.micrometer.core.instrument.Metrics;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import io.grpc.TlsChannelCredentials;
import org.signal.keytransparency.client.ConsistencyParameters;
import org.signal.keytransparency.client.KeyTransparencyQueryServiceGrpc;
import org.signal.keytransparency.client.MonitorKey;
import org.signal.keytransparency.client.MonitorRequest;
import org.signal.keytransparency.client.SearchRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.util.CompletableFutureUtil;
public class KeyTransparencyServiceClient implements Managed {
private static final String DAYS_UNTIL_CLIENT_CERTIFICATE_EXPIRATION_GAUGE_NAME =
MetricsUtil.name(KeyTransparencyServiceClient.class, "daysUntilClientCertificateExpiration");
private static final Logger logger = LoggerFactory.getLogger(KeyTransparencyServiceClient.class);
private final Executor callbackExecutor;
private final String host;
private final int port;
@@ -39,19 +52,63 @@ public class KeyTransparencyServiceClient implements Managed {
final String host,
final int port,
final String tlsCertificate,
final String clientCertificate,
final String clientPrivateKey,
final Executor callbackExecutor
) throws IOException {
this.host = host;
this.port = port;
try (final ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(
tlsCertificate.getBytes(StandardCharsets.UTF_8))) {
tlsCertificate.getBytes(StandardCharsets.UTF_8));
final ByteArrayInputStream clientCertificateInputStream = new ByteArrayInputStream(
clientCertificate.getBytes(StandardCharsets.UTF_8));
final ByteArrayInputStream clientPrivateKeyInputStream = new ByteArrayInputStream(
clientPrivateKey.getBytes(StandardCharsets.UTF_8))
) {
tlsChannelCredentials = TlsChannelCredentials.newBuilder()
.trustManager(certificateInputStream)
.keyManager(clientCertificateInputStream, clientPrivateKeyInputStream)
.build();
configureClientCertificateMetrics(clientCertificate);
}
this.callbackExecutor = callbackExecutor;
}
private void configureClientCertificateMetrics(String clientCertificate) {
try {
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
final Collection<? extends Certificate> certificates = cf.generateCertificates(
new ByteArrayInputStream(clientCertificate.getBytes(StandardCharsets.UTF_8)));
if (certificates.isEmpty()) {
logger.warn("No client certificate found");
return;
}
if (certificates.size() > 1) {
throw new IllegalArgumentException("Unexpected number of client certificates: " + certificates.size());
}
final Certificate certificate = certificates.iterator().next();
if (certificate instanceof X509Certificate x509Cert) {
final Instant expiration = Instant.ofEpochMilli(x509Cert.getNotAfter().getTime());
Metrics.gauge(DAYS_UNTIL_CLIENT_CERTIFICATE_EXPIRATION_GAUGE_NAME,
this,
(ignored) -> Duration.between(Instant.now(), expiration).toDays());
} else {
logger.error("Certificate was of unexpected type: {}", certificate.getClass().getName());
}
} catch (CertificateException e) {
throw new AssertionError("JDKs are required to support X.509 algorithms", e);
}
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public CompletableFuture<byte[]> search(
final ByteString searchKey,