hyperledger / hyperledger/fabric
Trailing zeros are trimmed after the data is hashed which brakes integrity checks
- Dominant language
- Go
- Stars
- 16.7k
- Forks
- 9.1k
- Avg merge
- 5h 38m
- Merged PRs (30d)
- 26
Description
### Description
### JSON input **with** trailing zero(s) is being added to a Private Data Collection (PDC):
1. First, a JSON input **with** trailing zero(s) is hashed to record the hash on the ledger.
2. Next, the trailing zeros get trimmed resulting in a modified JSON.
3. This new value is stored in a PDC.
4. When retrieved from a PDC, its hash will no longer match the hash of the original input. This brakes integrity checks.
### Example:
#### JSON Input before hashing
`{"assetId":"BUG","value":1.400}`
#### JSON stored in PDC
`{"assetId":"BUG","value":1.4}`
Tested on Fabric 2.3.0 + CouchDB 3.1.1, Fabric 2.5.0 + CouchDB 3.2.2
### Steps to reproduce
### Prerequisites
#### To demonstrate the issue, Fabric [samples](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) were used.
- sample network setup files located in `fabric-samples/test-network`
- sample java chaincode project located in `fabric-samples/asset-transfer-private-data/chaincode-java`was modified
#### In the chaincode project:
- Genson dependency was added to `build.gradle` file:
`implementation group: 'com.owlike', name: 'genson', version: '1.6'`
- All existing DataTypes and Contracts were replaced with the following Contract file:
AssetContract.java
```java
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.privatedata;
import com.owlike.genson.Genson;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Transaction;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
import org.hyperledger.fabric.shim.ChaincodeException;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
@Contract(name = "AssetContract")
@Default
public final class AssetContract implements ContractInterface {
private static final String ASSET_COLLECTION_NAME = "assetCollection";
private static final String ASSET_ID_BUG = "BUG";
private static final String ASSET_ID_OK = "OK";
private static final String DIGEST_ALGORITHM = "SHA-256";
private final Genson genson = new Genson();
@Transaction(name = "WriteBug", intent = Transaction.TYPE.SUBMIT)
public void writeBug(final Context ctx) {
Asset asset = new Asset(ASSET_ID_BUG, new BigDecimal("1.400"));
String assetJson = genson.serialize(asset);
System.out.printf("Put asset: %s, collection: %s, ID: %s\n", assetJson, ASSET_COLLECTION_NAME, ASSET_ID_BUG);
ctx.getStub().putPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_BUG, assetJson);
}
@Transaction(name = "ValidateBug", intent = Transaction.TYPE.EVALUATE)
public void validateBug(final Context ctx) throws Exception {
byte[] assetJson = ctx.getStub().getPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_BUG);
byte[] realHash = MessageDigest.getInstance(DIGEST_ALGORITHM).digest(assetJson);
byte[] expectedHash = ctx.getStub().getPrivateDataHash(ASSET_COLLECTION_NAME, ASSET_ID_BUG);
System.out.println("Real json: " + new String(assetJson));
System.out.println("Real hash: " + new String(realHash));
System.out.println("Expected hash: " + new String(expectedHash));
if (!Objects.deepEquals(realHash, expectedHash)) {
throw new ChaincodeException("Ledger hash is not equal to hash of private data");
}
}
@Transaction(name = "WriteOk", intent = Transaction.TYPE.SUBMIT)
public void writeOk(final Context ctx) {
Asset asset = new Asset(ASSET_ID_OK, new BigDecimal("1.4"));
String assetJson = genson.serialize(asset);
System.out.printf("Put asset: %s, collection: %s, ID: %s\n", assetJson, ASSET_COLLECTION_NAME, ASSET_ID_OK);
ctx.getStub().putPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_OK, assetJson);
}
@Transaction(name = "ValidateOk", intent = Transaction.TYPE.EVALUATE)
public void validateOk(final Context ctx) throws ChaincodeException, NoSuchAlgorithmException {
byte[] assetJson = ctx.getStub().getPrivateData(ASSET_COLLECTION_NAME, ASSET_ID_OK);
byte[] realHash = MessageDigest.getInstance(DIGEST_ALGORITHM).digest(assetJson);
byte[] expectedHash = ctx.getStub().getPrivateDataHash(ASSET_COLLECTION_NAME, ASSET_ID_OK);
System.out.println("Real json: " + new String(assetJson));
System.out.println("Real hash: " + new String(realHash));
System.out.println("Expected hash: " + new String(expectedHash));
if (!Objects.deepEquals(realHash, expectedHash)) {
throw new ChaincodeException("Ledger hash is not equal to hash of private data");
}
}
}
@DataType()
final class Asset {
@Property()
private final String assetId;
@Property()
private final BigDecimal value;
Asset(final String assetId,
final BigDecimal value) {
this.assetId = assetId;
this.value = value;
}
public String getAssetId() {
return assetId;
}
public BigDecimal getValue() {
return value;
}
}
```
### To start the network and deploy the chaincode, execute:
```bash
export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
./network.sh up createChannel -ca -s couchdb
./network.sh deployCC -ccp ../asset-transfer-private-data/chaincode-java -ccl java -ccn private -cccg ../asset-transfer-private-data/chaincode-java/collections_config.json
```
### To reproduce the bug, execute:
```bash
peer chaincode invoke -c '{"function":"WriteBug","Args":[]}' -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
peer chaincode invoke -c '{"function":"ValidateBug","Args":[]}' -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
```
You should see this:
```bash
Error: endorsement failure during invoke. response: status:500 message:"Ledger hash is not equal to hash of private data"
```
Contributor guide
Assessment
This issue has not been assessed yet.