Search in sources :

Example 41 with NodeInfo

use of com.quorum.tessera.partyinfo.node.NodeInfo in project tessera by ConsenSys.

the class DiscoveryHelperImpl method buildCurrent.

@Override
public NodeInfo buildCurrent() {
    final URI uri = RuntimeContext.getInstance().getP2pServerUri();
    final NodeUri nodeUri = NodeUri.create(uri);
    final List<ActiveNode> activeNodes = networkStore.getActiveNodes().collect(Collectors.toList());
    Set<Recipient> recipients = activeNodes.stream().filter(a -> !a.getKeys().isEmpty()).flatMap(a -> a.getKeys().stream().map(k -> Recipient.of(k, a.getUri().asString()))).collect(Collectors.toSet());
    NodeInfo nodeInfo = NodeInfo.Builder.create().withRecipients(recipients).withUrl(nodeUri.asString()).withSupportedApiVersions(ApiVersion.versions()).build();
    LOGGER.debug("Built nodeinfo {}", nodeInfo);
    return nodeInfo;
}
Also used : KeyNotFoundException(com.quorum.tessera.encryption.KeyNotFoundException) PublicKey(com.quorum.tessera.encryption.PublicKey) Logger(org.slf4j.Logger) DiscoveryHelper(com.quorum.tessera.discovery.DiscoveryHelper) LoggerFactory(org.slf4j.LoggerFactory) Set(java.util.Set) ApiVersion(com.quorum.tessera.version.ApiVersion) NodeUri(com.quorum.tessera.discovery.NodeUri) Collectors(java.util.stream.Collectors) Recipient(com.quorum.tessera.partyinfo.node.Recipient) ActiveNode(com.quorum.tessera.discovery.ActiveNode) NetworkStore(com.quorum.tessera.discovery.NetworkStore) List(java.util.List) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) RuntimeContext(com.quorum.tessera.context.RuntimeContext) Enclave(com.quorum.tessera.enclave.Enclave) Optional(java.util.Optional) URI(java.net.URI) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) NodeUri(com.quorum.tessera.discovery.NodeUri) Recipient(com.quorum.tessera.partyinfo.node.Recipient) ActiveNode(com.quorum.tessera.discovery.ActiveNode) URI(java.net.URI)

Example 42 with NodeInfo

use of com.quorum.tessera.partyinfo.node.NodeInfo in project tessera by ConsenSys.

the class PartyInfoResource method getPartyInfoKeys.

@Operation(summary = "/partyinfo/keys", operationId = "getPartiesPublicKeys", description = "get public keys of all known nodes in the network, including the server's own keys")
@ApiResponse(responseCode = "200", description = "known nodes' public keys", content = @Content(schema = @Schema(implementation = GetPublicKeysResponse.class)))
@GET
@Path("/keys")
@Produces(MediaType.APPLICATION_JSON)
public Response getPartyInfoKeys() {
    final NodeInfo current = this.discovery.getCurrent();
    final JsonArrayBuilder recipientBuilder = Json.createArrayBuilder();
    current.getRecipients().stream().map(recipient -> Json.createObjectBuilder().add("key", recipient.getKey().encodeToBase64()).build()).forEach(recipientBuilder::add);
    final String output = Json.createObjectBuilder().add("keys", recipientBuilder.build()).build().toString();
    return Response.status(Response.Status.OK).entity(output).build();
}
Also used : Schema(io.swagger.v3.oas.annotations.media.Schema) JsonArrayBuilder(jakarta.json.JsonArrayBuilder) GET(jakarta.ws.rs.GET) Json(jakarta.json.Json) Path(jakarta.ws.rs.Path) Content(io.swagger.v3.oas.annotations.media.Content) Discovery(com.quorum.tessera.discovery.Discovery) Operation(io.swagger.v3.oas.annotations.Operation) Response(jakarta.ws.rs.core.Response) MediaType(jakarta.ws.rs.core.MediaType) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) Objects.requireNonNull(java.util.Objects.requireNonNull) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse) Tag(io.swagger.v3.oas.annotations.tags.Tag) GetPublicKeysResponse(com.quorum.tessera.thirdparty.model.GetPublicKeysResponse) Produces(jakarta.ws.rs.Produces) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) JsonArrayBuilder(jakarta.json.JsonArrayBuilder) Path(jakarta.ws.rs.Path) Produces(jakarta.ws.rs.Produces) GET(jakarta.ws.rs.GET) Operation(io.swagger.v3.oas.annotations.Operation) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse)

Example 43 with NodeInfo

use of com.quorum.tessera.partyinfo.node.NodeInfo in project tessera by ConsenSys.

the class PartyInfoResource method partyInfo.

/**
 * Update the local partyinfo store with the encoded partyinfo included in the request.
 *
 * @param payload The encoded partyinfo information pushed by the caller
 * @return an empty 200 OK Response if the local node is using remote key validation; a 200 OK
 *     Response wrapping an encoded partyinfo that contains only the local node's URL if not using
 *     remote key validation; a 500 Internal Server Error if remote key validation fails
 */
@Operation(summary = "/partyinfo", operationId = "broadcastPartyInfo", description = "broadcast partyinfo information to server")
@ApiResponse(responseCode = "200", description = "server successfully updated its party info", content = @Content(array = @ArraySchema(schema = @Schema(description = "empty if server is using remote key validation, else is encoded partyinfo object containing only the server's URL", type = "string", format = "byte"))))
@ApiResponse(responseCode = "500", description = "Validation failed (if server is using remote key validation)")
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response partyInfo(@RequestBody(required = true, description = "partyinfo object") final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) @Parameter(description = "client's supported API versions", array = @ArraySchema(schema = @Schema(type = "string"))) final List<String> headers) {
    final PartyInfo partyInfo = partyInfoParser.from(payload);
    final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream().filter(Objects::nonNull).flatMap(v -> Arrays.stream(v.split(","))).collect(Collectors.toSet());
    final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions);
    LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl());
    if (!enableKeyValidation) {
        LOGGER.debug("Key validation not enabled, passing PartyInfo through");
        discovery.onUpdate(nodeInfo);
        partyInfo.getParties().stream().map(Party::getUrl).map(NodeUri::create).map(NodeUri::asURI).forEach(partyStore::store);
        // create an empty party info object with our URL to send back
        // this is used by older versions (before 0.10.0), but we don't want to give any info back
        final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet());
        final byte[] returnData = partyInfoParser.to(emptyInfo);
        return Response.ok(returnData).build();
    }
    final PublicKey localPublicKey = enclave.defaultPublicKey();
    final Predicate<Recipient> isValidRecipient = r -> {
        try {
            LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl());
            final String dataToEncrypt = UUID.randomUUID().toString();
            final EncodedPayload encodedPayload = enclave.encryptPayload(dataToEncrypt.getBytes(), localPublicKey, List.of(r.getKey()), PrivacyMetadata.Builder.forStandardPrivate().build());
            final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload);
            try (Response response = restClient.target(r.getUrl()).path("partyinfo").path("validate").request().post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) {
                LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl());
                final String responseData = response.readEntity(String.class);
                final boolean isValid = Objects.equals(responseData, dataToEncrypt);
                if (!isValid) {
                    LOGGER.warn("Validation of key {} for peer {} failed.  Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl());
                    LOGGER.debug("Response from {} was {}", r.getUrl(), responseData);
                }
                return isValid;
            }
        // Assume any and all exceptions to mean invalid. enclave bubbles up nacl array out of
        // bounds when calculating shared key from invalid data
        } catch (Exception ex) {
            LOGGER.debug(null, ex);
            return false;
        }
    };
    final String partyInfoSender = partyInfo.getUrl();
    final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender));
    // Validate caller and treat no valid certs as security issue.
    final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream().filter(isSender.and(isValidRecipient)).map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())).collect(Collectors.toSet());
    LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys);
    if (validatedSendersKeys.isEmpty()) {
        throw new SecurityException("No validated keys found for peer " + partyInfoSender);
    }
    // End validation stuff
    final NodeInfo reducedNodeInfo = NodeInfo.Builder.create().withUrl(partyInfoSender).withSupportedApiVersions(versions).withRecipients(validatedSendersKeys).build();
    discovery.onUpdate(reducedNodeInfo);
    partyInfo.getParties().stream().map(Party::getUrl).map(NodeUri::create).map(NodeUri::asURI).forEach(partyStore::store);
    return Response.ok().build();
}
Also used : PublicKey(com.quorum.tessera.encryption.PublicKey) java.util(java.util) NodeInfoUtil(com.quorum.tessera.partyinfo.model.NodeInfoUtil) LoggerFactory(org.slf4j.LoggerFactory) Party(com.quorum.tessera.partyinfo.model.Party) GetPartyInfoResponse(com.quorum.tessera.p2p.model.GetPartyInfoResponse) Content(io.swagger.v3.oas.annotations.media.Content) Discovery(com.quorum.tessera.discovery.Discovery) Operation(io.swagger.v3.oas.annotations.Operation) Response(jakarta.ws.rs.core.Response) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) Objects.requireNonNull(java.util.Objects.requireNonNull) RequestBody(io.swagger.v3.oas.annotations.parameters.RequestBody) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse) Constants(com.quorum.tessera.shared.Constants) Schema(io.swagger.v3.oas.annotations.media.Schema) PartyInfoParser(com.quorum.tessera.p2p.partyinfo.PartyInfoParser) Client(jakarta.ws.rs.client.Client) Logger(org.slf4j.Logger) Collections.emptySet(java.util.Collections.emptySet) Collections.emptyList(java.util.Collections.emptyList) Predicate(java.util.function.Predicate) com.quorum.tessera.enclave(com.quorum.tessera.enclave) JsonArrayBuilder(jakarta.json.JsonArrayBuilder) NodeUri(com.quorum.tessera.discovery.NodeUri) jakarta.ws.rs(jakarta.ws.rs) Collectors(java.util.stream.Collectors) PartyStore(com.quorum.tessera.p2p.partyinfo.PartyStore) Json(jakarta.json.Json) Entity(jakarta.ws.rs.client.Entity) Parameter(io.swagger.v3.oas.annotations.Parameter) ArraySchema(io.swagger.v3.oas.annotations.media.ArraySchema) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) MediaType(jakarta.ws.rs.core.MediaType) Tag(io.swagger.v3.oas.annotations.tags.Tag) Recipient(com.quorum.tessera.partyinfo.model.Recipient) PublicKey(com.quorum.tessera.encryption.PublicKey) NodeUri(com.quorum.tessera.discovery.NodeUri) Recipient(com.quorum.tessera.partyinfo.model.Recipient) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) GetPartyInfoResponse(com.quorum.tessera.p2p.model.GetPartyInfoResponse) Response(jakarta.ws.rs.core.Response) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) Operation(io.swagger.v3.oas.annotations.Operation) ApiResponse(io.swagger.v3.oas.annotations.responses.ApiResponse)

Example 44 with NodeInfo

use of com.quorum.tessera.partyinfo.node.NodeInfo in project tessera by ConsenSys.

the class PartyInfoResourceTest method partyInfo.

@Test
public void partyInfo() {
    String url = "http://www.bogus.com";
    PublicKey myKey = PublicKey.from("myKey".getBytes());
    PublicKey recipientKey = PublicKey.from("recipientKey".getBytes());
    String message = "I love sparrows";
    byte[] payload = message.getBytes();
    Recipient recipient = Recipient.of(recipientKey, url);
    Set<Recipient> recipientList = Collections.singleton(recipient);
    PartyInfo partyInfo = new PartyInfo(url, recipientList, Collections.emptySet());
    when(partyInfoParser.from(payload)).thenReturn(partyInfo);
    when(enclave.defaultPublicKey()).thenReturn(myKey);
    when(partyInfoParser.to(partyInfo)).thenReturn(payload);
    EncodedPayload encodedPayload = mock(EncodedPayload.class);
    List<String> uuidList = new ArrayList<>();
    doAnswer((invocation) -> {
        byte[] d = invocation.getArgument(0);
        uuidList.add(new String(d));
        return encodedPayload;
    }).when(enclave).encryptPayload(any(byte[].class), any(PublicKey.class), anyList(), any(PrivacyMetadata.class));
    when(payloadEncoder.encode(encodedPayload)).thenReturn(payload);
    WebTarget webTarget = mock(WebTarget.class);
    when(restClient.target(url)).thenReturn(webTarget);
    when(webTarget.path(anyString())).thenReturn(webTarget);
    Invocation.Builder invocationBuilder = mock(Invocation.Builder.class);
    when(webTarget.request()).thenReturn(invocationBuilder);
    Response response = mock(Response.class);
    when(response.getStatus()).thenReturn(200);
    doAnswer((invocation) -> uuidList.get(0)).when(response).readEntity(String.class);
    when(invocationBuilder.post(any(Entity.class))).thenReturn(response);
    Response result = partyInfoResource.partyInfo(payload, List.of("v1,v2"));
    assertThat(result.getStatus()).isEqualTo(200);
    verify(partyInfoParser).from(payload);
    verify(enclave).defaultPublicKey();
    verify(enclave).encryptPayload(any(byte[].class), any(PublicKey.class), anyList(), any(PrivacyMetadata.class));
    verify(payloadEncoder).encode(encodedPayload);
    verify(restClient).target(url);
    ArgumentCaptor<NodeInfo> argCaptor = ArgumentCaptor.forClass(NodeInfo.class);
    verify(discovery).onUpdate(argCaptor.capture());
    final NodeInfo nodeInfo = argCaptor.getValue();
    assertThat(nodeInfo).isNotNull();
    assertThat(nodeInfo.getUrl()).isEqualTo(url);
    assertThat(nodeInfo.supportedApiVersions()).containsExactlyInAnyOrder("v1", "v2");
}
Also used : Entity(jakarta.ws.rs.client.Entity) Invocation(jakarta.ws.rs.client.Invocation) PublicKey(com.quorum.tessera.encryption.PublicKey) Recipient(com.quorum.tessera.partyinfo.model.Recipient) EncodedPayload(com.quorum.tessera.enclave.EncodedPayload) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) Response(jakarta.ws.rs.core.Response) PrivacyMetadata(com.quorum.tessera.enclave.PrivacyMetadata) NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) WebTarget(jakarta.ws.rs.client.WebTarget) Test(org.junit.Test)

Example 45 with NodeInfo

use of com.quorum.tessera.partyinfo.node.NodeInfo in project tessera by ConsenSys.

the class PartyInfoBroadcasterTest method exceptionThrowByPostDoesntBubble.

@Test
public void exceptionThrowByPostDoesntBubble() {
    final NodeInfo partyInfo = NodeInfo.Builder.create().withUrl(OWN_URL).build();
    when(partyStore.getParties()).thenReturn(Set.of(URI.create(TARGET_URL), URI.create(TARGET_URL_2)));
    doReturn(partyInfo).when(discovery).getCurrent();
    doThrow(UnsupportedOperationException.class).when(p2pClient).sendPartyInfo(TARGET_URL, DATA);
    final Throwable throwable = catchThrowable(partyInfoBroadcaster::run);
    assertThat(throwable).isNull();
    verify(partyStore).loadFromConfigIfEmpty();
    verify(partyStore).getParties();
    verify(p2pClient).sendPartyInfo(TARGET_URL, DATA);
    verify(p2pClient).sendPartyInfo(TARGET_URL_2, DATA);
    verify(discovery).getCurrent();
    verify(partyInfoParser).to(any(PartyInfo.class));
}
Also used : NodeInfo(com.quorum.tessera.partyinfo.node.NodeInfo) Assertions.catchThrowable(org.assertj.core.api.Assertions.catchThrowable) PartyInfo(com.quorum.tessera.partyinfo.model.PartyInfo) Test(org.junit.Test)

Aggregations

NodeInfo (com.quorum.tessera.partyinfo.node.NodeInfo)49 Test (org.junit.Test)37 PublicKey (com.quorum.tessera.encryption.PublicKey)31 Recipient (com.quorum.tessera.partyinfo.node.Recipient)19 ActiveNode (com.quorum.tessera.discovery.ActiveNode)12 Response (jakarta.ws.rs.core.Response)11 PartyInfo (com.quorum.tessera.partyinfo.model.PartyInfo)10 NodeUri (com.quorum.tessera.discovery.NodeUri)8 URI (java.net.URI)8 Collectors (java.util.stream.Collectors)8 RuntimeContext (com.quorum.tessera.context.RuntimeContext)7 EncodedPayload (com.quorum.tessera.enclave.EncodedPayload)7 Set (java.util.Set)7 KeyNotFoundException (com.quorum.tessera.encryption.KeyNotFoundException)6 Logger (org.slf4j.Logger)6 Discovery (com.quorum.tessera.discovery.Discovery)5 DiscoveryHelper (com.quorum.tessera.discovery.DiscoveryHelper)5 NetworkStore (com.quorum.tessera.discovery.NetworkStore)5 Enclave (com.quorum.tessera.enclave.Enclave)5 Entity (jakarta.ws.rs.client.Entity)5