Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for mandatory recipients #1325

Merged
merged 26 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ea5c6d7
Add another privacy mode between PP and PSV called MandatoryRecipients
namtruong Aug 3, 2021
3742b4f
Add data to PrivacyMetadata and relevant validations
namtruong Aug 3, 2021
03a8c07
Add extra data to encoded payload when enclave encrypts
namtruong Aug 3, 2021
5539971
Increase version api to 4.0
namtruong Aug 3, 2021
22046ed
Include mandatory recipients in send/receive business objects
namtruong Aug 3, 2021
c847ee8
MandatoryRecipientsNotSupportedException
namtruong Aug 3, 2021
7606be1
Reject transaction if remote peer does not support the feature
namtruong Aug 3, 2021
7f23137
Update tests
namtruong Aug 3, 2021
bbe14fc
Extra data in remote enclave
namtruong Aug 3, 2021
1602eba
mandatory recipients in json objects
namtruong Aug 3, 2021
faafefa
Update transaction resources to handle mandatory recipients
namtruong Aug 3, 2021
3147262
mandatory recipients included in privacymetadata
namtruong Aug 3, 2021
6cdbbc0
gradle applySpotless
namtruong Aug 3, 2021
99cb9bf
extra privacy validations
namtruong Aug 3, 2021
078834e
Merge branch 'master' of https://github.com/ConsenSys/tessera into su…
namtruong Aug 16, 2021
885e922
revert the changes so this resource only handle mime type v3 of the p…
namtruong Aug 16, 2021
16500e1
revert the changes so this resource only handle mime type v3 of the p…
namtruong Aug 16, 2021
b5c8303
integration tests for send/receive with mandatory recipients
namtruong Aug 16, 2021
8f32b5e
TransactionResource4 to deal with mimetype 4.0 json
namtruong Aug 16, 2021
fc21ace
correct client
namtruong Aug 16, 2021
da2bed1
improve acceptance tests
namtruong Aug 16, 2021
e7144b8
spotlessApply
namtruong Aug 16, 2021
e672146
loosen restrictions on mr validations to include rather than equals
namtruong Aug 16, 2021
e51faa6
Fix CVE-2021-28170
namtruong Aug 16, 2021
92bb9b6
revert version change
namtruong Aug 16, 2021
f7fe1de
Merge branch 'master' into support-mandatory-recipients
namtruong Aug 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public EncodedPayload encryptPayload(
.withPrivacyMode(privacyMetadata.getPrivacyMode())
.withAffectedContractTransactions(affectedContractTransactionHashes)
.withExecHash(privacyMetadata.getExecHash())
.withMandatoryRecipients(privacyMetadata.getMandatoryRecipients())
.build();
}

Expand Down Expand Up @@ -149,6 +150,7 @@ public EncodedPayload encryptPayload(
.withPrivacyMode(privacyMetadata.getPrivacyMode())
.withAffectedContractTransactions(affectedContractTransactionHashes)
.withExecHash(privacyMetadata.getExecHash())
.withMandatoryRecipients(privacyMetadata.getMandatoryRecipients())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class EncodedPayload {

private final PrivacyGroup.Id privacyGroupId;

private final Set<PublicKey> mandatoryRecipients;

private EncodedPayload(
final PublicKey senderKey,
final byte[] cipherText,
Expand All @@ -38,7 +40,8 @@ private EncodedPayload(
final PrivacyMode privacyMode,
final Map<TxHash, SecurityHash> affectedContractTransactions,
final byte[] execHash,
final PrivacyGroup.Id privacyGroupId) {
final PrivacyGroup.Id privacyGroupId,
final Set<PublicKey> mandatoryRecipients) {
this.senderKey = senderKey;
this.cipherText = cipherText;
this.cipherTextNonce = cipherTextNonce;
Expand All @@ -49,6 +52,7 @@ private EncodedPayload(
this.affectedContractTransactions = affectedContractTransactions;
this.execHash = execHash;
this.privacyGroupId = privacyGroupId;
this.mandatoryRecipients = mandatoryRecipients;
}

public PublicKey getSenderKey() {
Expand Down Expand Up @@ -91,6 +95,10 @@ public Optional<PrivacyGroup.Id> getPrivacyGroupId() {
return Optional.ofNullable(privacyGroupId);
}

public Set<PublicKey> getMandatoryRecipients() {
return mandatoryRecipients;
}

public static class Builder {

private Builder() {}
Expand Down Expand Up @@ -145,6 +153,8 @@ public static Builder from(EncodedPayload encodedPayload) {

private PrivacyGroup.Id privacyGroupId;

private Set<PublicKey> mandatoryRecipients = Collections.emptySet();

public Builder withSenderKey(final PublicKey senderKey) {
this.senderKey = senderKey;
return this;
Expand Down Expand Up @@ -227,6 +237,11 @@ public Builder withPrivacyGroupId(final PrivacyGroup.Id privacyGroupId) {
return this;
}

public Builder withMandatoryRecipients(final Set<PublicKey> mandatoryRecipients) {
this.mandatoryRecipients = mandatoryRecipients;
return this;
}

public EncodedPayload build() {

Map<TxHash, SecurityHash> affectedTransactions =
Expand All @@ -242,6 +257,12 @@ public EncodedPayload build() {
throw new RuntimeException("ExecutionHash data is invalid");
}

if ((privacyMode == PrivacyMode.MANDATORY_RECIPIENTS) == mandatoryRecipients.isEmpty()) {
throw new RuntimeException(
"Mandatory recipients data only applicable for Mandatory Recipients privacy mode. "
+ "In case no mandatory recipient is required, consider using Party Protection privacy mode");
}

return new EncodedPayload(
senderKey,
cipherText,
Expand All @@ -252,7 +273,8 @@ public EncodedPayload build() {
privacyMode,
affectedTransactions,
execHash,
privacyGroupId);
privacyGroupId,
mandatoryRecipients);
}
}

Expand All @@ -269,7 +291,8 @@ public boolean equals(Object o) {
&& Objects.equals(recipientKeys, that.recipientKeys)
&& privacyMode == that.privacyMode
&& Arrays.equals(execHash, that.execHash)
&& Objects.equals(privacyGroupId, that.privacyGroupId);
&& Objects.equals(privacyGroupId, that.privacyGroupId)
&& Objects.equals(mandatoryRecipients, that.mandatoryRecipients);
}

@Override
Expand All @@ -282,7 +305,8 @@ public int hashCode() {
recipientNonce,
recipientKeys,
privacyMode,
privacyGroupId);
privacyGroupId,
mandatoryRecipients);
result = 31 * result + Arrays.hashCode(cipherText);
result = 31 * result + Arrays.hashCode(execHash);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ public byte[] encode(final EncodedPayload payload) {
executionHash = encodeField(payload.getExecHash());
}

byte[] mandatoryRecipients = new byte[0];
if (payload.getPrivacyMode() == PrivacyMode.MANDATORY_RECIPIENTS) {
mandatoryRecipients =
encodeArray(
payload.getMandatoryRecipients().stream()
.map(PublicKey::getKeyBytes)
.collect(Collectors.toUnmodifiableList()));
}

byte[] privacyGroupId =
payload
.getPrivacyGroupId()
Expand All @@ -70,6 +79,7 @@ public byte[] encode(final EncodedPayload payload) {
+ privacyModeByte.length
+ affectedContractsPayloadLength
+ executionHash.length
+ mandatoryRecipients.length
+ privacyGroupId.length)
.put(senderKey)
.put(cipherText)
Expand All @@ -80,6 +90,7 @@ public byte[] encode(final EncodedPayload payload) {
.put(privacyModeByte)
.put(affectedContractTxs.array())
.put(executionHash)
.put(mandatoryRecipients)
.put(privacyGroupId)
.array();
}
Expand Down Expand Up @@ -113,17 +124,20 @@ public EncodedPayload decode(final byte[] input) {
final byte[] recipientNonce = new byte[Math.toIntExact(recipientNonceSize)];
buffer.get(recipientNonce);

EncodedPayload.Builder payloadBuilder = EncodedPayload.Builder.create();

payloadBuilder
.withSenderKey(PublicKey.from(senderKey))
.withCipherText(cipherText)
.withCipherTextNonce(nonce)
.withRecipientBoxes(recipientBoxes)
.withRecipientNonce(recipientNonce);

// this means there are no recipients in the payload (which we receive when we are a
// participant)
// TODO - not sure this is right
if (!buffer.hasRemaining()) {

return EncodedPayload.Builder.create()
.withSenderKey(PublicKey.from(senderKey))
.withCipherText(cipherText)
.withCipherTextNonce(nonce)
.withRecipientBoxes(recipientBoxes)
.withRecipientNonce(recipientNonce)
return payloadBuilder
.withRecipientKeys(emptyList())
.withPrivacyMode(PrivacyMode.STANDARD_PRIVATE)
.withAffectedContractTransactions(emptyMap())
Expand All @@ -141,15 +155,10 @@ public EncodedPayload decode(final byte[] input) {
recipientKeys.add(box);
}

if (!buffer.hasRemaining()) {
payloadBuilder.withRecipientKeys(recipientKeys.stream().map(PublicKey::from).collect(toList()));

return EncodedPayload.Builder.create()
.withSenderKey(PublicKey.from(senderKey))
.withCipherText(cipherText)
.withCipherTextNonce(nonce)
.withRecipientBoxes(recipientBoxes)
.withRecipientNonce(recipientNonce)
.withRecipientKeys(recipientKeys.stream().map(PublicKey::from).collect(toList()))
if (!buffer.hasRemaining()) {
return payloadBuilder
.withPrivacyMode(PrivacyMode.STANDARD_PRIVATE)
.withAffectedContractTransactions(emptyMap())
.withExecHash(new byte[0])
Expand Down Expand Up @@ -185,36 +194,40 @@ public EncodedPayload decode(final byte[] input) {
}
}

payloadBuilder
.withPrivacyMode(privacyMode)
.withAffectedContractTransactions(affectedContractTransactions)
.withExecHash(executionHash);

if (buffer.hasRemaining()) {
if (privacyMode == PrivacyMode.MANDATORY_RECIPIENTS) {
final long mandatoryRecipientLength = buffer.getLong();

final List<byte[]> mandatoryRecipients = new ArrayList<>();
for (long i = 0; i < mandatoryRecipientLength; i++) {
final long boxSize = buffer.getLong();
final byte[] box = new byte[Math.toIntExact(boxSize)];
buffer.get(box);
mandatoryRecipients.add(box);
}
payloadBuilder.withMandatoryRecipients(
mandatoryRecipients.stream().map(PublicKey::from).collect(Collectors.toSet()));
}
}

if (!buffer.hasRemaining()) {
return EncodedPayload.Builder.create()
.withSenderKey(PublicKey.from(senderKey))
.withCipherText(cipherText)
.withCipherTextNonce(nonce)
.withRecipientBoxes(recipientBoxes)
.withRecipientNonce(recipientNonce)
.withRecipientKeys(recipientKeys.stream().map(PublicKey::from).collect(toList()))
.withPrivacyMode(privacyMode)
.withAffectedContractTransactions(affectedContractTransactions)
.withExecHash(executionHash)
.build();
return payloadBuilder.build();
}

final long privacyGroupIdSize = buffer.getLong();
final byte[] privacyGroupId = new byte[Math.toIntExact(privacyGroupIdSize)];
buffer.get(privacyGroupId);

return EncodedPayload.Builder.create()
.withSenderKey(PublicKey.from(senderKey))
.withCipherText(cipherText)
.withCipherTextNonce(nonce)
.withRecipientBoxes(recipientBoxes)
.withRecipientNonce(recipientNonce)
.withRecipientKeys(recipientKeys.stream().map(PublicKey::from).collect(toList()))
.withPrivacyMode(privacyMode)
.withAffectedContractTransactions(affectedContractTransactions)
.withExecHash(executionHash)
.withPrivacyGroupId(PrivacyGroup.Id.fromBytes(privacyGroupId))
.build();
if (privacyGroupId.length > 0) {
payloadBuilder.withPrivacyGroupId(PrivacyGroup.Id.fromBytes(privacyGroupId));
}

return payloadBuilder.build();
}

@Override
Expand Down Expand Up @@ -252,7 +265,8 @@ public EncodedPayload forRecipient(final EncodedPayload payload, final PublicKey
.withRecipientKeys(recipientList)
.withPrivacyMode(payload.getPrivacyMode())
.withAffectedContractTransactions(affectedTxnMap)
.withExecHash(payload.getExecHash());
.withExecHash(payload.getExecHash())
.withMandatoryRecipients(payload.getMandatoryRecipients());
payload.getPrivacyGroupId().ifPresent(builder::withPrivacyGroupId);

return builder.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.quorum.tessera.enclave;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import com.quorum.tessera.encryption.PublicKey;
import java.util.*;

public interface PrivacyMetadata {

Expand All @@ -15,6 +13,8 @@ public interface PrivacyMetadata {

Optional<PrivacyGroup.Id> getPrivacyGroupId();

Set<PublicKey> getMandatoryRecipients();

class Builder {

private PrivacyMode privacyMode;
Expand All @@ -25,6 +25,8 @@ class Builder {

private PrivacyGroup.Id privacyGroupId;

private Set<PublicKey> mandatoryRecipients = Collections.emptySet();

public static Builder create() {
return new Builder();
}
Expand All @@ -49,6 +51,11 @@ public Builder withPrivacyGroupId(PrivacyGroup.Id privacyGroupId) {
return this;
}

public Builder withMandatoryRecipients(Set<PublicKey> mandatoryRecipients) {
this.mandatoryRecipients = mandatoryRecipients;
return this;
}

public PrivacyMetadata build() {

Objects.requireNonNull(privacyMode, "privacyMode is required");
Expand All @@ -58,6 +65,12 @@ public PrivacyMetadata build() {
throw new RuntimeException("ExecutionHash data is invalid");
}

if ((privacyMode == PrivacyMode.MANDATORY_RECIPIENTS) == mandatoryRecipients.isEmpty()) {
throw new RuntimeException(
"Mandatory recipients data only applicable for Mandatory Recipients privacy mode. "
+ "In case no mandatory recipient is required, consider using Party Protection privacy mode");
}

return new PrivacyMetadata() {
@Override
public PrivacyMode getPrivacyMode() {
Expand All @@ -78,6 +91,11 @@ public byte[] getExecHash() {
public Optional<PrivacyGroup.Id> getPrivacyGroupId() {
return Optional.ofNullable(privacyGroupId);
}

@Override
public Set<PublicKey> getMandatoryRecipients() {
return Set.copyOf(mandatoryRecipients);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public enum PrivacyMode {
STANDARD_PRIVATE(0),
PARTY_PROTECTION(1),
MANDATORY_RECIPIENTS(2),
PRIVATE_STATE_VALIDATION(3);

private final int privacyFlag;
Expand Down
Loading