use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.
the class EnclaveKeySynchroniserImpl method syncKeys.
@Override
public void syncKeys() {
NodeUri nodeUri = Optional.of(RuntimeContext.getInstance()).map(RuntimeContext::getP2pServerUri).map(NodeUri::create).get();
List<ActiveNode> activeNodes = networkStore.getActiveNodes().filter(a -> a.getUri().equals(nodeUri)).collect(Collectors.toList());
if (activeNodes.isEmpty()) {
return;
}
final Set<PublicKey> storedKeys = activeNodes.stream().flatMap(a -> a.getKeys().stream()).collect(Collectors.toSet());
final Set<PublicKey> keys = enclave.getPublicKeys();
if (!storedKeys.equals(keys)) {
final Set<PublicKey> allKeys = Stream.concat(storedKeys.stream(), keys.stream()).collect(Collectors.toUnmodifiableSet());
activeNodes.forEach(activeNode -> {
ActiveNode modified = ActiveNode.Builder.from(activeNode).withKeys(allKeys).build();
networkStore.store(modified);
});
}
}
use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.
the class P2PRestApp method getSingletons.
@Override
public Set<Object> getSingletons() {
RuntimeContext runtimeContext = RuntimeContext.getInstance();
List<URI> peers = runtimeContext.getPeers();
LOGGER.debug("Found configured peers {}", peers);
peers.stream().map(NodeUri::create).map(NodeUri::asURI).peek(u -> LOGGER.debug("Adding {} to party store", u)).forEach(partyStore::store);
final PartyInfoResource partyInfoResource = new PartyInfoResource(discovery, partyInfoParser, runtimeContext.getP2pClient(), enclave, runtimeContext.isRemoteKeyValidation());
final IPWhitelistFilter iPWhitelistFilter = new IPWhitelistFilter();
final TransactionResource transactionResource = new TransactionResource(transactionManager, batchResendManager, legacyResendManager);
final UpCheckResource upCheckResource = new UpCheckResource();
final PrivacyGroupResource privacyGroupResource = new PrivacyGroupResource(privacyGroupManager);
if (runtimeContext.isRecoveryMode()) {
final RecoveryResource recoveryResource = new RecoveryResource(transactionManager, batchResendManager);
return Set.of(partyInfoResource, iPWhitelistFilter, recoveryResource, upCheckResource);
}
return Set.of(partyInfoResource, iPWhitelistFilter, transactionResource, privacyGroupResource, upCheckResource);
}
use of com.quorum.tessera.discovery.NodeUri 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();
}
use of com.quorum.tessera.discovery.NodeUri in project tessera by ConsenSys.
the class PartyInfoBroadcaster method run.
/**
* Iterates over all known parties and contacts them for the current state of their known node
* discovery list
*
* <p>For Tessera 0.9 backwards, after contacting the known parties, this poller then updates this
* nodes list of data with any new information collected.
*
* <p>This behaviour is now deprecated since the /partyinfo API call now has been made more strict
* with node validation to prevent exploiting the API to attack the Tessera network.
*
* <p>This call is merely to let its parties know about this node existence, any recipients that
* want to be added to this node's PartyInfo will need to make their own partyinfo call and
* validation
*/
@Override
public void run() {
LOGGER.info("Started PartyInfo polling round");
partyStore.loadFromConfigIfEmpty();
final NodeInfo nodeInfo = discovery.getCurrent();
final NodeUri ourUrl = NodeUri.create(nodeInfo.getUrl());
final PartyInfo partyInfo = PartyInfoBuilder.create().withUri(nodeInfo.getUrl()).withRecipients(nodeInfo.getRecipientsAsMap()).build();
final byte[] encodedPartyInfo = partyInfoParser.to(partyInfo);
LOGGER.debug("Contacting following peers with PartyInfo: {}", partyInfo.getParties());
LOGGER.debug("Sending party info {}", nodeInfo);
partyStore.getParties().stream().map(NodeUri::create).filter(url -> !ourUrl.equals(url)).forEach(url -> pollSingleParty(url.asString(), encodedPartyInfo));
LOGGER.info("Finished PartyInfo polling round");
}
Aggregations