Skip to content

Commit

Permalink
Merge "[FAB-7066] Modifying the enccc example chaincode"
Browse files Browse the repository at this point in the history
  • Loading branch information
C0rWin authored and Gerrit Code Review committed Nov 27, 2017
2 parents f1b70fa + 4a3f5ef commit eea424e
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 119 deletions.
38 changes: 26 additions & 12 deletions examples/chaincode/go/enccc_example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@

To test `EncCC` you need to first generate an AES 256 bit key as a base64
encoded string so that it can be passed as JSON to the peer chaincode
invoke's transient parameter
invoke's transient parameter.

Note: Before getting started you must use govendor to add external dependencies. Please issue the following commands inside the "enccc_example" folder:
```
ENCKEY=`openssl rand 32 -base64`
govendor init
govendor add +external
```

Let's generate the encryption and decryption keys. The example will simulate a shared key so the key is used for both encryption and decryption.
```
ENCKEY=`openssl rand 32 -base64` && DECKEY=$ENCKEY
```

At this point, you can invoke the chaincode to encrypt key-value pairs as
follows
follows:

Note: the following assumes the env is initialized and peer has joined channel "my-ch".
```
peer chaincode invoke -n enccc -C my-ch -c '{"Args":["ENC","PUT","key","value"]}' --transient "{\"ENCKEY\":\"$ENCKEY\"}"
peer chaincode invoke -n enccc -C my-ch -c '{"Args":["ENCRYPT","key1","value1"]}' --transient "{\"ENCKEY\":\"$ENCKEY\"}"
```

This call will encrypt using a random IV. This may be undesirable for
Expand All @@ -28,39 +36,45 @@ IV=`openssl rand 16 -base64`
Then, the IV may be specified in the transient field

```
peer chaincode invoke -n enccc -C my-ch -c '{"Args":["ENC","PUT","key","value"]}' --transient "{\"ENCKEY\":\"$ENCKEY\",\"IV\":\"$IV\"}"
peer chaincode invoke -n enccc -C my-ch -c '{"Args":["ENCRYPT","key2","value2"]}' --transient "{\"ENCKEY\":\"$ENCKEY\",\"IV\":\"$IV\"}"
```

Two such invocations will produce equal KVS writes, which can be endorsed by multiple nodes.

The value can be retrieved back as follows

```
peer chaincode query -n enccc -C my-ch -c '{"Args":["ENC","GET","key"]}' --transient "{\"ENCKEY\":\"$ENCKEY\"}"
peer chaincode query -n enccc -C my-ch -c '{"Args":["DECRYPT","key1"]}' --transient "{\"DECKEY\":\"$DECKEY\"}"
```
```
peer chaincode query -n enccc -C my-ch -c '{"Args":["DECRYPT","key2"]}' --transient "{\"DECKEY\":\"$DECKEY\"}"
```

Note that in this case we use a chaincode query operation; while the use of the
transient field guarantees that the content will not be written to the ledger,
the chaincode decrypts the message and puts it in the proposal response. An
invocation would persist the result in the ledger for all channel readers to
see whereas a query can be discarded and so the result remains confidential.

To test signing, you also need to generate an ECDSA key for the appopriate
curve, as follows
To test signing and verifying, you also need to generate an ECDSA key for the appopriate
curve, as follows.

```
SIGKEY=`openssl ecparam -name prime256v1 -genkey | tail -n5 | base64 -w0`
On Intel:
SIGKEY=`openssl ecparam -name prime256v1 -genkey | tail -n5 | base64 -w0` && VERKEY=$SIGKEY
On Mac:
SIGKEY=`openssl ecparam -name prime256v1 -genkey | tail -n5 | base64` && VERKEY=$SIGKEY
```

At this point, you can invoke the chaincode to sign and then encrypt key-value
pairs as follows

```
peer chaincode invoke -n enccc -C my-ch -c '{"Args":["SIG","PUT","key","value"]}' --logging-level debug -o 127.0.0.1:7050 --transient "{\"ENCKEY\":\"$ENCKEY\",\"SIGKEY\":\"$SIGKEY\"}"
peer chaincode invoke -n enccc -C my-ch -c '{"Args":["ENCRYPTSIGN","key3","value3"]}' --transient "{\"ENCKEY\":\"$ENCKEY\",\"SIGKEY\":\"$SIGKEY\"}"
```

And similarly to retrieve them using a query

```
peer chaincode query -n enccc -C my-ch -c '{"Args":["SIG","GET","key"]}' --logging-level debug -o 127.0.0.1:7050 --transient "{\"ENCKEY\":\"$ENCKEY\",\"SIGKEY\":\"$SIGKEY\"}"
peer chaincode query -n enccc -C my-ch -c '{"Args":["DECRYPTVERIFY","key3"]}' --transient "{\"DECKEY\":\"$DECKEY\",\"VERKEY\":\"$VERKEY\"}"
```
183 changes: 108 additions & 75 deletions examples/chaincode/go/enccc_example/enccc_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
pb "github.com/hyperledger/fabric/protos/peer"
)

const DECKEY = "DECKEY"
const VERKEY = "VERKEY"
const ENCKEY = "ENCKEY"
const SIGKEY = "SIGKEY"
const IV = "IV"
Expand All @@ -30,101 +32,112 @@ func (t *EncCC) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}

// Encrypter exposes two functions: "PUT" that shows how to write state to the ledger after having
// Encrypter exposes how to write state to the ledger after having
// encrypted it with an AES 256 bit key that has been provided to the chaincode through the
// transient field; and "GET" that shows how to read from the ledger and decrypt using an AES 256
// bit key that has been provided to the chaincode through the transient field.
func (t *EncCC) Encrypter(stub shim.ChaincodeStubInterface, f string, args []string, encKey, IV []byte) pb.Response {
// transient field
func (t *EncCC) Encrypter(stub shim.ChaincodeStubInterface, args []string, encKey, IV []byte) pb.Response {
// create the encrypter entity - we give it an ID, the bccsp instance, the key and (optionally) the IV
ent, err := entities.NewAES256EncrypterEntity("ID", t.bccspInst, encKey, IV)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %s", err))
}

switch f {
case "PUT":
if len(args) != 2 {
return shim.Error("Expected 2 parameters to PUT")
}
if len(args) != 2 {
return shim.Error("Expected 2 parameters to function Encrypter")
}

key := args[0]
cleartextValue := []byte(args[1])
key := args[0]
cleartextValue := []byte(args[1])

// here, we encrypt cleartextValue and assign it to key
err = encryptAndPutState(stub, ent, key, cleartextValue)
if err != nil {
return shim.Error(fmt.Sprintf("encryptAndPutState failed, err %+v", err))
}
// here, we encrypt cleartextValue and assign it to key
err = encryptAndPutState(stub, ent, key, cleartextValue)
if err != nil {
return shim.Error(fmt.Sprintf("encryptAndPutState failed, err %+v", err))
}
return shim.Success(nil)
}

return shim.Success(nil)
case "GET":
if len(args) != 1 {
return shim.Error("Expected 1 parameters to GET")
}
// Decrypter exposes how to read from the ledger and decrypt using an AES 256
// bit key that has been provided to the chaincode through the transient field.
func (t *EncCC) Decrypter(stub shim.ChaincodeStubInterface, args []string, decKey, IV []byte) pb.Response {
// create the encrypter entity - we give it an ID, the bccsp instance, the key and (optionally) the IV
ent, err := entities.NewAES256EncrypterEntity("ID", t.bccspInst, decKey, IV)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %s", err))
}

key := args[0]
if len(args) != 1 {
return shim.Error("Expected 1 parameters to function Decrypter")
}

// here we decrypt the state associated to key
cleartextValue, err := getStateAndDecrypt(stub, ent, key)
if err != nil {
return shim.Error(fmt.Sprintf("getStateAndDecrypt failed, err %+v", err))
}
key := args[0]

// here we return the decrypted value as a result
return shim.Success(cleartextValue)
default:
return shim.Error(fmt.Sprintf("Unsupported function %s", f))
// here we decrypt the state associated to key
cleartextValue, err := getStateAndDecrypt(stub, ent, key)
if err != nil {
return shim.Error(fmt.Sprintf("getStateAndDecrypt failed, err %+v", err))
}

// here we return the decrypted value as a result
return shim.Success(cleartextValue)
}

func (t *EncCC) EncrypterSigner(stub shim.ChaincodeStubInterface, f string, args []string, encKey, sigKey []byte) pb.Response {
// EncrypterSigner exposes how to write state to the ledger after having received keys for
// encrypting (AES 256 bit key) and signing (X9.62/SECG curve over a 256 bit prime field) that has been provided to the chaincode through the
// transient field
func (t *EncCC) EncrypterSigner(stub shim.ChaincodeStubInterface, args []string, encKey, sigKey []byte) pb.Response {
// create the encrypter/signer entity - we give it an ID, the bccsp instance and the keys
ent, err := entities.NewAES256EncrypterECDSASignerEntity("ID", t.bccspInst, encKey, sigKey)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %+v", err))
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %s", err))
}

switch f {
case "PUT":
if len(args) != 2 {
return shim.Error("Expected 2 parameters to PUT")
}
if len(args) != 2 {
return shim.Error("Expected 2 parameters to function EncrypterSigner")
}

key := args[0]
cleartextValue := []byte(args[1])
key := args[0]
cleartextValue := []byte(args[1])

// here, we sign cleartextValue, encrypt it and assign it to key
err = signEncryptAndPutState(stub, ent, key, cleartextValue)
if err != nil {
return shim.Error(fmt.Sprintf("signEncryptAndPutState failed, err %+v", err))
}
// here, we sign cleartextValue, encrypt it and assign it to key
err = signEncryptAndPutState(stub, ent, key, cleartextValue)
if err != nil {
return shim.Error(fmt.Sprintf("signEncryptAndPutState failed, err %+v", err))
}

return shim.Success(nil)
case "GET":
if len(args) != 1 {
return shim.Error("Expected 1 parameters to GET")
}
return shim.Success(nil)
}

key := args[0]
// DecrypterVerify exposes how to get state to the ledger after having received keys for
// decrypting (AES 256 bit key) and verifying (X9.62/SECG curve over a 256 bit prime field) that has been provided to the chaincode through the
// transient field
func (t *EncCC) DecrypterVerify(stub shim.ChaincodeStubInterface, args []string, decKey, verKey []byte) pb.Response {
// create the decrypter/verify entity - we give it an ID, the bccsp instance and the keys
ent, err := entities.NewAES256EncrypterECDSASignerEntity("ID", t.bccspInst, decKey, verKey)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256DecrypterEntity failed, err %s", err))
}

// here we decrypt the state associated to key and verify it
cleartextValue, err := getStateDecryptAndVerify(stub, ent, key)
if err != nil {
return shim.Error(fmt.Sprintf("getStateDecryptAndVerify failed, err %+v", err))
}
if len(args) != 1 {
return shim.Error("Expected 1 parameters to function DecrypterVerify")
}
key := args[0]

// here we return the decrypted and verified value as a result
return shim.Success(cleartextValue)
default:
return shim.Error(fmt.Sprintf("Unsupported function %s", f))
// here we decrypt the state associated to key and verify it
cleartextValue, err := getStateDecryptAndVerify(stub, ent, key)
if err != nil {
return shim.Error(fmt.Sprintf("getStateDecryptAndVerify failed, err %+v", err))
}

// here we return the decrypted and verified value as a result
return shim.Success(cleartextValue)
}

// RangeDecrypter shows how range queries may be satisfied by using the encrypter
// RangeDecrypter shows how range queries may be satisfied by using the decrypter
// entity directly to decrypt previously encrypted key-value pairs
func (t *EncCC) RangeDecrypter(stub shim.ChaincodeStubInterface, encKey []byte) pb.Response {
func (t *EncCC) RangeDecrypter(stub shim.ChaincodeStubInterface, decKey []byte) pb.Response {
// create the encrypter entity - we give it an ID, the bccsp instance and the key
ent, err := entities.NewAES256EncrypterEntity("ID", t.bccspInst, encKey, nil)
ent, err := entities.NewAES256EncrypterEntity("ID", t.bccspInst, decKey, nil)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %s", err))
}
Expand All @@ -137,9 +150,10 @@ func (t *EncCC) RangeDecrypter(stub shim.ChaincodeStubInterface, encKey []byte)
return shim.Success(bytes)
}

// Invoke for this chaincode exposes two functions: "ENC" to demonstrate
// the use of an Entity that can encrypt, and "SIG" to demonstrate the use
// of an Entity that can encrypt and sign
// Invoke for this chaincode exposes functions to ENCRYPT, DECRYPT transactional
// data. It also supports an example to ENCRYPT and SIGN and DECRYPT and
// VERIFY. The Initialization Vector (IV) can be passed in as a parm to
// ensure peers have deterministic data.
func (t *EncCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// get arguments and transient
f, args := stub.GetFunctionAndParameters()
Expand All @@ -149,15 +163,24 @@ func (t *EncCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
}

switch f {
case "ENC":
case "ENCRYPT":
// make sure there's a key in transient - the assumption is that
// it's associated to the string "ENCKEY"
if _, in := tMap[ENCKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient key %s", ENCKEY))
return shim.Error(fmt.Sprintf("Expected transient encryption key %s", ENCKEY))
}

return t.Encrypter(stub, args[0:], tMap[ENCKEY], tMap[IV])
case "DECRYPT":

// make sure there's a key in transient - the assumption is that
// it's associated to the string "DECKEY"
if _, in := tMap[DECKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient decryption key %s", DECKEY))
}

return t.Encrypter(stub, args[0], args[1:], tMap[ENCKEY], tMap[IV])
case "SIG":
return t.Decrypter(stub, args[0:], tMap[DECKEY], tMap[IV])
case "ENCRYPTSIGN":
// make sure keys are there in the transient map - the assumption is that they
// are associated to the string "ENCKEY" and "SIGKEY"
if _, in := tMap[ENCKEY]; !in {
Expand All @@ -166,15 +189,25 @@ func (t *EncCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Error(fmt.Sprintf("Expected transient key %s", SIGKEY))
}

return t.EncrypterSigner(stub, args[0], args[1:], tMap[ENCKEY], tMap[SIGKEY])
case "RANGE":
return t.EncrypterSigner(stub, args[0:], tMap[ENCKEY], tMap[SIGKEY])
case "DECRYPTVERIFY":
// make sure keys are there in the transient map - the assumption is that they
// are associated to the string "DECKEY" and "VERKEY"
if _, in := tMap[DECKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient key %s", DECKEY))
} else if _, in := tMap[VERKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient key %s", VERKEY))
}

return t.DecrypterVerify(stub, args[0:], tMap[DECKEY], tMap[VERKEY])
case "RANGEQUERY":
// make sure there's a key in transient - the assumption is that
// it's associated to the string "ENCKEY"
if _, in := tMap[ENCKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient key %s", ENCKEY))
if _, in := tMap[DECKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient key %s", DECKEY))
}

return t.RangeDecrypter(stub, tMap[ENCKEY])
return t.RangeDecrypter(stub, tMap[DECKEY])
default:
return shim.Error(fmt.Sprintf("Unsupported function %s", f))
}
Expand Down
Loading

0 comments on commit eea424e

Please sign in to comment.