Go quorum enclave (#1599)

* add enclave for goQuorum privacy using Tessera

Signed-off-by: Stefan Pingel <stefan.pingel@consensys.net>
pull/1608/head
Stefan Pingel 4 years ago committed by GitHub
parent 393482012a
commit a4a8723b69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      enclave/build.gradle
  2. 98
      enclave/src/integration-test/java/org/hyperledger/besu/enclave/GoQuorumEnclaveTest.java
  3. 3
      enclave/src/main/java/org/hyperledger/besu/enclave/Enclave.java
  4. 25
      enclave/src/main/java/org/hyperledger/besu/enclave/EnclaveFactory.java
  5. 145
      enclave/src/main/java/org/hyperledger/besu/enclave/GoQuorumEnclave.java
  6. 6
      enclave/src/main/java/org/hyperledger/besu/enclave/RequestTransmitter.java
  7. 12
      enclave/src/main/java/org/hyperledger/besu/enclave/VertxRequestTransmitter.java
  8. 55
      enclave/src/main/java/org/hyperledger/besu/enclave/types/GoQuorumReceiveResponse.java
  9. 48
      enclave/src/main/java/org/hyperledger/besu/enclave/types/GoQuorumSendRequest.java

@ -10,7 +10,6 @@ dependencies {
runtimeOnly('org.bouncycastle:bcpkix-jdk15on')
// test dependencies.
testImplementation project(':testutil')
@ -21,8 +20,7 @@ dependencies {
integrationTestImplementation project(':testutil')
integrationTestImplementation 'org.bouncycastle:bcpkix-jdk15on'
integrationTestImplementation 'org.awaitility:awaitility'
integrationTestImplementation 'org.mockito:mockito-core'
integrationTestImplementation 'junit:junit'
integrationTestImplementation 'net.consensys:orion'

@ -0,0 +1,98 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.enclave;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse;
import org.hyperledger.besu.enclave.types.SendResponse;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import io.vertx.core.Vertx;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
public class GoQuorumEnclaveTest {
private static final byte[] PAYLOAD = Base64.getDecoder().decode("EAAAAAAA");
private static final String MOCK_KEY = "iOCzoGo5kwtZU0J41Z9xnGXHN6ZNukIa9MspvHtu3Jk=";
private static final String KEY = "key";
private static GoQuorumEnclave enclave;
private RequestTransmitter vertxTransmitter;
@Before
public void setUp() {
enclave = createGoQuorumEnclaveWithMockRequestTransmitter();
}
@Test
public void testUpCheck() {
when(vertxTransmitter.get(any(), any(), ArgumentMatchers.contains("/upcheck"), any()))
.thenReturn("I'm up!");
assertThat(enclave.upCheck()).isTrue();
}
@Test
public void testReceiveThrowsWhenPayloadDoesNotExist() {
when(vertxTransmitter.get(any(), any(), ArgumentMatchers.contains("/receive"), any()))
.thenThrow(
new EnclaveClientException(404, "Message with hash " + MOCK_KEY + " was not found"));
assertThatThrownBy(() -> enclave.receive(MOCK_KEY))
.isInstanceOf(EnclaveClientException.class)
.hasMessageContaining("Message with hash " + MOCK_KEY + " was not found");
}
@Test
public void testSendAndReceive() {
when(vertxTransmitter.post(any(), any(), any(), any())).thenReturn(new SendResponse(KEY));
when(vertxTransmitter.get(any(), any(), ArgumentMatchers.contains("/receive"), any()))
.thenReturn(new GoQuorumReceiveResponse(PAYLOAD, 0, null, null));
final List<String> publicKeys = Arrays.asList("/+UuD63zItL1EbjxkKUljMgG8Z1w0AJ8pNOR4iq2yQc=");
final SendResponse sr = enclave.send(PAYLOAD, publicKeys.get(0), publicKeys);
assertThat(sr.getKey()).isEqualTo(KEY);
final GoQuorumReceiveResponse rr = enclave.receive(sr.getKey());
assertThat(rr).isNotNull();
assertThat(rr.getPayload()).isEqualTo(PAYLOAD);
}
@Test
public void upcheckReturnsFalseIfNoResponseReceived() throws URISyntaxException {
final Vertx vertx = Vertx.vertx();
final EnclaveFactory factory = new EnclaveFactory(vertx);
assertThat(factory.createGoQuorumEnclave(new URI("http://8.8.8.8:65535")).upCheck()).isFalse();
}
private GoQuorumEnclave createGoQuorumEnclaveWithMockRequestTransmitter() {
vertxTransmitter = mock(RequestTransmitter.class);
return new GoQuorumEnclave(vertxTransmitter);
}
}

@ -48,7 +48,8 @@ public class Enclave {
public boolean upCheck() {
try {
final String upcheckResponse = requestTransmitter.get("/upcheck", this::handleRawResponse);
final String upcheckResponse =
requestTransmitter.get(null, null, "/upcheck", this::handleRawResponse);
return upcheckResponse.equals("I'm up!");
} catch (final Exception e) {
return false;

@ -48,6 +48,31 @@ public class EnclaveFactory {
return new Enclave(vertxTransmitter);
}
public GoQuorumEnclave createGoQuorumEnclave(final URI enclaveUri) {
final HttpClientOptions clientOptions = createNonTlsClientOptions(enclaveUri);
final RequestTransmitter vertxTransmitter =
new VertxRequestTransmitter(vertx.createHttpClient(clientOptions));
return new GoQuorumEnclave(vertxTransmitter);
}
public GoQuorumEnclave createGoQuorumEnclave(
final URI enclaveUri,
final Path privacyKeyStoreFile,
final Path privacyKeyStorePasswordFile,
final Path privacyAllowlistFile) {
final HttpClientOptions clientOptions =
createTlsClientOptions(
enclaveUri, privacyKeyStoreFile, privacyKeyStorePasswordFile, privacyAllowlistFile);
final RequestTransmitter vertxTransmitter =
new VertxRequestTransmitter(vertx.createHttpClient(clientOptions));
return new GoQuorumEnclave(vertxTransmitter);
}
private HttpClientOptions createNonTlsClientOptions(final URI enclaveUri) {
if (enclaveUri.getPort() == -1) {

@ -0,0 +1,145 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.enclave;
import org.hyperledger.besu.enclave.RequestTransmitter.ResponseBodyHandler;
import org.hyperledger.besu.enclave.types.GoQuorumReceiveResponse;
import org.hyperledger.besu.enclave.types.GoQuorumSendRequest;
import org.hyperledger.besu.enclave.types.ReceiveRequest;
import org.hyperledger.besu.enclave.types.SendResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GoQuorumEnclave {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String JSON = "application/json";
private final RequestTransmitter requestTransmitter;
public GoQuorumEnclave(final RequestTransmitter requestTransmitter) {
this.requestTransmitter = requestTransmitter;
}
public boolean upCheck() {
try {
final String upcheckResponse =
requestTransmitter.get(null, null, "/upcheck", this::handleRawResponse);
return upcheckResponse.equals("I'm up!");
} catch (final Exception e) {
return false;
}
}
public SendResponse send(
final byte[] payload, final String privateFrom, final List<String> privateFor) {
final GoQuorumSendRequest request = new GoQuorumSendRequest(payload, privateFrom, privateFor);
return post(
JSON,
request,
"/send",
(statusCode, body) -> handleJsonResponse(statusCode, body, SendResponse.class, 201));
}
public GoQuorumReceiveResponse receive(final String payloadKey) {
final ReceiveRequest request = new ReceiveRequest(payloadKey);
return get(
JSON,
request,
"/receive",
(statusCode, body) ->
handleJsonResponse(statusCode, body, GoQuorumReceiveResponse.class, 200));
}
private <T> T post(
final String mediaType,
final Object content,
final String endpoint,
final ResponseBodyHandler<T> responseBodyHandler) {
final String bodyText;
try {
bodyText = objectMapper.writeValueAsString(content);
} catch (final JsonProcessingException e) {
throw new EnclaveClientException(400, "Unable to serialize request.");
}
return requestTransmitter.post(mediaType, bodyText, endpoint, responseBodyHandler);
}
private <T> T get(
final String mediaType,
final Object content,
final String endpoint,
final ResponseBodyHandler<T> responseBodyHandler) {
final String bodyText;
try {
bodyText = objectMapper.writeValueAsString(content);
} catch (final JsonProcessingException e) {
throw new EnclaveClientException(400, "Unable to serialize request.");
}
final T t = requestTransmitter.get(mediaType, bodyText, endpoint, responseBodyHandler);
return t;
}
private <T> T handleJsonResponse(
final int statusCode,
final byte[] body,
final Class<T> responseType,
final int expectedStatusCode) {
if (isSuccess(statusCode, expectedStatusCode)) {
return parseResponse(statusCode, body, responseType);
} else if (clientError(statusCode)) {
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8);
throw new EnclaveClientException(statusCode, utf8EncodedBody);
} else {
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8);
throw new EnclaveServerException(statusCode, utf8EncodedBody);
}
}
private <T> T parseResponse(
final int statusCode, final byte[] body, final Class<T> responseType) {
try {
return objectMapper.readValue(body, responseType);
} catch (final IOException e) {
final String utf8EncodedBody = new String(body, StandardCharsets.UTF_8);
throw new EnclaveClientException(statusCode, utf8EncodedBody);
}
}
private boolean clientError(final int statusCode) {
return statusCode >= 400 && statusCode < 500;
}
private boolean isSuccess(final int statusCode, final int expectedStatusCode) {
return statusCode == expectedStatusCode;
}
private String handleRawResponse(final int statusCode, final byte[] body) {
final String bodyText = new String(body, StandardCharsets.UTF_8);
if (isSuccess(statusCode, 200)) {
return bodyText;
}
throw new EnclaveClientException(
statusCode, String.format("Request failed with %d; body={%s}", statusCode, bodyText));
}
}

@ -27,5 +27,9 @@ public interface RequestTransmitter {
String endpoint,
ResponseBodyHandler<T> responseBodyHandler);
<T> T get(String endpoint, ResponseBodyHandler<T> responseBodyHandler);
<T> T get(
String mediaType,
String content,
String endpoint,
ResponseBodyHandler<T> responseBodyHandler);
}

@ -44,9 +44,17 @@ public class VertxRequestTransmitter implements RequestTransmitter {
}
@Override
public <T> T get(final String endpoint, final ResponseBodyHandler<T> responseHandler) {
public <T> T get(
final String contentType,
final String content,
final String endpoint,
final ResponseBodyHandler<T> responseHandler) {
return sendRequest(
HttpMethod.GET, Optional.empty(), Optional.empty(), endpoint, responseHandler);
HttpMethod.GET,
Optional.ofNullable(contentType),
Optional.ofNullable(content),
endpoint,
responseHandler);
}
protected <T> T sendRequest(

@ -0,0 +1,55 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.enclave.types;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class GoQuorumReceiveResponse {
private final byte[] payload;
private final int privacyFlag;
private final String affectedContractTransactions[];
private final String execHash;
@JsonCreator
public GoQuorumReceiveResponse(
@JsonProperty(value = "payload") final byte[] payload,
@JsonProperty(value = "privacyFlag") final int privacyFlag,
@JsonProperty(value = "affectedContractTransactions")
final String affectedContractTransactions[],
@JsonProperty(value = "execHash") final String execHash) {
this.payload = payload;
this.privacyFlag = privacyFlag;
this.affectedContractTransactions = affectedContractTransactions;
this.execHash = execHash;
}
public byte[] getPayload() {
return payload;
}
public int getPrivacyFlag() {
return privacyFlag;
}
public String[] getAffectedContractTransactions() {
return affectedContractTransactions;
}
public String getExecHash() {
return execHash;
}
}

@ -0,0 +1,48 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.enclave.types;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"payload", "from", "to"})
public class GoQuorumSendRequest {
private final byte[] payload;
private final String from;
private final List<String> to;
public GoQuorumSendRequest(
@JsonProperty(value = "payload") final byte[] payload,
@JsonProperty(value = "from") final String from,
@JsonProperty(value = "to") final List<String> to) {
this.payload = payload;
this.from = from;
this.to = to;
}
public byte[] getPayload() {
return payload;
}
public String getFrom() {
return from;
}
public List<String> getTo() {
return to;
}
}
Loading…
Cancel
Save