mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 16:57:40 +03:00
Introduce a helper to limit signing to commands (#9050)
This allows the signing interceptor to be generic for now (which is useful to test), while allowing the users to use a simple static method to only sign commands directed to the command and command submittion services. This is intended to address https://github.com/digital-asset/daml/pull/9036#discussion_r588332093 https://github.com/digital-asset/daml/pull/9036 should be rebased on the main branch after this has been merged. changelog_begin changelog_end
This commit is contained in:
parent
0c4d8ac19c
commit
45b33757b2
@ -13,6 +13,7 @@ da_java_library(
|
||||
"//:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//language-support/java/bindings:bindings-java",
|
||||
"//runtime-components/non-repudiation-core",
|
||||
"@maven//:com_google_guava_guava",
|
||||
"@maven//:io_grpc_grpc_api",
|
||||
|
@ -3,12 +3,19 @@
|
||||
|
||||
package com.daml.nonrepudiation.client;
|
||||
|
||||
import com.daml.ledger.api.v1.CommandServiceGrpc;
|
||||
import com.daml.ledger.api.v1.CommandSubmissionServiceGrpc;
|
||||
import com.daml.nonrepudiation.Fingerprints;
|
||||
import com.daml.nonrepudiation.Headers;
|
||||
import com.daml.nonrepudiation.Signatures;
|
||||
import io.grpc.*;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A gRPC client-side interceptor that uses a key pair to sign a payload and adds it as metadata to
|
||||
@ -19,50 +26,72 @@ public final class SigningInterceptor implements ClientInterceptor {
|
||||
private final PrivateKey key;
|
||||
private final byte[] fingerprint;
|
||||
private final String algorithm;
|
||||
private final Predicate<MethodDescriptor<?, ?>> signingPredicate;
|
||||
|
||||
public SigningInterceptor(PrivateKey key, X509Certificate certificate) {
|
||||
private static final Set<String> commandIssuingServices =
|
||||
Collections.unmodifiableSet(
|
||||
new HashSet<>(
|
||||
Arrays.asList(
|
||||
CommandServiceGrpc.SERVICE_NAME, CommandSubmissionServiceGrpc.SERVICE_NAME)));
|
||||
|
||||
public static SigningInterceptor signCommands(PrivateKey key, X509Certificate certificate) {
|
||||
return new SigningInterceptor(
|
||||
key, certificate, method -> commandIssuingServices.contains(method.getServiceName()));
|
||||
}
|
||||
|
||||
// This is package private as it's not intended for general use and exists for testing
|
||||
// exclusively.
|
||||
SigningInterceptor(
|
||||
PrivateKey key,
|
||||
X509Certificate certificate,
|
||||
Predicate<MethodDescriptor<?, ?>> signingPredicate) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.algorithm = certificate.getSigAlgName();
|
||||
this.fingerprint = Fingerprints.compute(certificate);
|
||||
this.signingPredicate = signingPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||
public final <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
|
||||
ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
|
||||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
|
||||
private Listener<RespT> responseListener = null;
|
||||
private Metadata headers = null;
|
||||
private int requested = 0;
|
||||
if (signingPredicate.test(method)) {
|
||||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
|
||||
private Listener<RespT> responseListener = null;
|
||||
private Metadata headers = null;
|
||||
private int requested = 0;
|
||||
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
// Delay start until we have the message body since
|
||||
// the signature in the Metadata depends on the body.
|
||||
this.responseListener = responseListener;
|
||||
this.headers = headers;
|
||||
}
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
// Delay start until we have the message body since
|
||||
// the signature in the Metadata depends on the body.
|
||||
this.responseListener = responseListener;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void request(int numMessages) {
|
||||
// Delay until we have the message body since the
|
||||
// signature in the Metadata depends on the body.
|
||||
requested += numMessages;
|
||||
}
|
||||
@Override
|
||||
public void request(int numMessages) {
|
||||
// Delay until we have the message body since the
|
||||
// signature in the Metadata depends on the body.
|
||||
requested += numMessages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(ReqT request) {
|
||||
byte[] requestBytes =
|
||||
ByteMarshaller.INSTANCE.parse(method.getRequestMarshaller().stream(request));
|
||||
byte[] signature = Signatures.sign(algorithm, key, requestBytes);
|
||||
headers.put(Headers.SIGNATURE, signature);
|
||||
headers.put(Headers.ALGORITHM, algorithm);
|
||||
headers.put(Headers.FINGERPRINT, fingerprint);
|
||||
delegate().start(responseListener, headers);
|
||||
delegate().request(requested);
|
||||
delegate().sendMessage(request);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void sendMessage(ReqT request) {
|
||||
byte[] requestBytes =
|
||||
ByteMarshaller.INSTANCE.parse(method.getRequestMarshaller().stream(request));
|
||||
byte[] signature = Signatures.sign(algorithm, key, requestBytes);
|
||||
headers.put(Headers.SIGNATURE, signature);
|
||||
headers.put(Headers.ALGORITHM, algorithm);
|
||||
headers.put(Headers.FINGERPRINT, fingerprint);
|
||||
delegate().start(responseListener, headers);
|
||||
delegate().request(requested);
|
||||
delegate().sendMessage(request);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ final class StubOwner private (
|
||||
)
|
||||
} yield CommandSubmissionServiceGrpc
|
||||
.blockingStub(builders.proxyChannel.build())
|
||||
.withInterceptors(new SigningInterceptor(key, certificate))
|
||||
.withInterceptors(SigningInterceptor.signCommands(key, certificate))
|
||||
|
||||
stubOwner.acquire()(context)
|
||||
|
||||
|
@ -87,7 +87,7 @@ final class NonRepudiationProxyConformance
|
||||
testCases = ConformanceTestCases,
|
||||
participants = Vector(proxyChannel),
|
||||
commandInterceptors = Seq(
|
||||
new SigningInterceptor(key, certificate)
|
||||
SigningInterceptor.signCommands(key, certificate)
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -29,6 +29,7 @@ da_scala_library(
|
||||
"//libs-scala/resources-akka",
|
||||
"//libs-scala/resources-grpc",
|
||||
"//runtime-components/non-repudiation",
|
||||
"//runtime-components/non-repudiation-client",
|
||||
"//runtime-components/non-repudiation-postgresql",
|
||||
"//runtime-components/non-repudiation-resources",
|
||||
"@maven//:com_zaxxer_HikariCP",
|
||||
|
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.nonrepudiation.client
|
||||
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
object TestSigningInterceptors {
|
||||
|
||||
private[nonrepudiation] def signEverything(
|
||||
key: PrivateKey,
|
||||
certificate: X509Certificate,
|
||||
): SigningInterceptor =
|
||||
new SigningInterceptor(key, certificate, _ => true)
|
||||
|
||||
}
|
@ -9,7 +9,7 @@ import java.time.{Clock, Instant, ZoneId}
|
||||
|
||||
import com.daml.grpc.test.GrpcServer
|
||||
import com.daml.nonrepudiation.SignedPayloadRepository.KeyEncoder
|
||||
import com.daml.nonrepudiation.client.SigningInterceptor
|
||||
import com.daml.nonrepudiation.client.TestSigningInterceptors
|
||||
import io.grpc.inprocess.{InProcessChannelBuilder, InProcessServerBuilder}
|
||||
import io.grpc.{Channel, StatusRuntimeException}
|
||||
import org.scalatest.Inside
|
||||
@ -78,7 +78,7 @@ final class NonRepudiationProxySpec
|
||||
val result =
|
||||
Health.getHealthStatus(
|
||||
proxyChannel,
|
||||
new SigningInterceptor(privateKey, certificate),
|
||||
TestSigningInterceptors.signEverything(privateKey, certificate),
|
||||
)
|
||||
|
||||
result shouldEqual Health.getHealthStatus(channel)
|
||||
@ -135,7 +135,7 @@ final class NonRepudiationProxySpec
|
||||
the[StatusRuntimeException] thrownBy {
|
||||
Health.getHealthStatus(
|
||||
proxyChannel,
|
||||
new SigningInterceptor(privateKey, certificate),
|
||||
TestSigningInterceptors.signEverything(privateKey, certificate),
|
||||
)
|
||||
} should have message SignatureVerificationFailed.asRuntimeException.getMessage
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user