Skip to content

Commit

Permalink
[FAB-830] - Sample chaincode-level crypto
Browse files Browse the repository at this point in the history
This change set contains a sample chaincode that uses the chaincode-level
crypto primitives introduced through FAB-830. In particular, it shows how
 - to perform encryption/decryption of selected key/value pairs using a key
   passed through the transient field
 - to perform encryption/decryption and sign/verify of selected key/value pairs
   using keys passed through the transient field
 - to perform range query decryption of all key/value pairs using a key passed
   through the transient field

A set of tests and a readme file explaining how to test the chaincode are also
included.

Change-Id: I10f0b5ddb3dab3e347c256a5ee8b058794747b3a
Signed-off-by: Alessandro Sorniotti <ale.linux@sopit.net>
  • Loading branch information
ale-linux committed Sep 20, 2017
1 parent 0e495ee commit e198a5e
Show file tree
Hide file tree
Showing 3 changed files with 499 additions and 0 deletions.
48 changes: 48 additions & 0 deletions examples/chaincode/go/enccc_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Using EncCC

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

```
ENCKEY=`openssl rand 32 -base64`
```

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

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

The value can be retrieved back as follows

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

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

```
SIGKEY=`openssl ecparam -name prime256v1 -genkey | tail -n5 | base64 -w0`
```

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\"}"
```

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\"}"
```
245 changes: 245 additions & 0 deletions examples/chaincode/go/enccc_example/enccc_example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"encoding/json"
"fmt"

"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/factory"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/chaincode/shim/ext/encshim"
"github.com/hyperledger/fabric/core/chaincode/shim/ext/entities"
pb "github.com/hyperledger/fabric/protos/peer"
)

const ENCKEY = "ENCKEY"
const SIGKEY = "SIGKEY"

// EncCC example simple Chaincode implementation of a chaincode that uses encshim
type EncCC struct {
bccspInst bccsp.BCCSP
}

// Init does nothing for this cc
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
// 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 []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)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %s", err))
}

// create the encrypted shim - we give it the stub we received from the cc
es, err := encshim.NewEncShim(stub)

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

// here, we encrypt []byte(args[1]) and assign it to args[0]
err = es.With(ent).PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("encshim.PutState failed, err %s", err))
}

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

// here we decrypt the state associated to args[0]
val, err := es.With(ent).GetState(args[0])
if err != nil {
return shim.Error(fmt.Sprintf("encshim.GetState failed, err %s", err))
}

// here we return the decrypted value bas a result
return shim.Success(val)
default:
return shim.Error(fmt.Sprintf("Unsupported function %s", f))
}
}

func (t *EncCC) EncrypterSigner(stub shim.ChaincodeStubInterface, f string, 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 %s", err))
}

// create the encrypted shim - we give it the stub we received from the cc
es, err := encshim.NewEncShim(stub)

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

// here we create a SignedMessage, set its payload
// to []byte(args[1]) and the ID of the entity and
// sign it with the entity
msg := &entities.SignedMessage{Payload: []byte(args[1]), ID: []byte(ent.ID())}
err = msg.Sign(ent)
if err != nil {
return shim.Error(fmt.Sprintf("msg.sign failed, err %s", err))
}

// here we serialize the SignedMessage
b, err := msg.ToBytes()
if err != nil {
return shim.Error(fmt.Sprintf("msg.toBytes failed, err %s", err))
}

// here we encrypt the serialized version associated to args[0]
err = es.With(ent).PutState(args[0], b)
if err != nil {
return shim.Error(fmt.Sprintf("encshim.PutState failed, err %s", err))
}

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

// here we decrypt the state associated to args[0]
val, err := es.With(ent).GetState(args[0])
if err != nil {
return shim.Error(fmt.Sprintf("encshim.GetState failed, err %s", err))
}

// we unmarshal a SignedMessage from the decrypted state
msg := &entities.SignedMessage{}
err = msg.FromBytes(val)
if err != nil {
return shim.Error(fmt.Sprintf("msg.fromBytes failed, err %s", err))
}

// we verify the signature
ok, err := msg.Verify(ent)
if err != nil {
return shim.Error(fmt.Sprintf("msg.verify failed, err %s", err))
} else if !ok {
return shim.Error("invalid signature")
}

// if all goes well, we return the (decrypted and verified) payload
return shim.Success(msg.Payload)
default:
return shim.Error(fmt.Sprintf("Unsupported function %s", f))
}
}

type keyValuePair struct {
Key string `json:"key"`
Value []byte `json:"value"`
}

// RangeDecrypter shows how range queries may be satisfied by using the encrypter
// entity directly to decrypt previously encrypted key-value pairs
func (t *EncCC) RangeDecrypter(stub shim.ChaincodeStubInterface, encKey []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)
if err != nil {
return shim.Error(fmt.Sprintf("entities.NewAES256EncrypterEntity failed, err %s", err))
}

// we call get state by range to go through the entire range
iterator, err := stub.GetStateByRange("", "")
if err != nil {
return shim.Error(fmt.Sprintf("stub.GetStateByRange failed, err %s", err))
}
defer iterator.Close()

// we decrypt each entry - the assumption is that they have all been encrypted with the same key
keyvalueset := []keyValuePair{}
for iterator.HasNext() {
el, err := iterator.Next()
if err != nil {
return shim.Error(fmt.Sprintf("iterator.Next failed, err %s", err))
}

v, err := ent.Decrypt(el.Value)
if err != nil {
return shim.Error(fmt.Sprintf("ent.Decrypt failed, err %s", err))
}

keyvalueset = append(keyvalueset, keyValuePair{el.Key, v})
}

bytes, err := json.Marshal(keyvalueset)
if err != nil {
return shim.Error(fmt.Sprintf("json.Marshal failed, err %s", err))
}

return shim.Success(bytes)
}

// Invoke for this chaincode exposes two functions: "ENC" to demonstrate
// the use of encshim with an Entity that can encrypt, and "SIG" to
// demonstrate the use of encshim with an Entity that can encrypt and sign
func (t *EncCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// get arguments and transient
f, args := stub.GetFunctionAndParameters()
tMap, err := stub.GetTransient()
if err != nil {
return shim.Error(fmt.Sprintf("Could not retrieve transient, err %s", err))
}

switch f {
case "ENC":
// 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 t.Encrypter(stub, args[0], args[1:], tMap[ENCKEY])
case "SIG":
// 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 {
return shim.Error(fmt.Sprintf("Expected transient key %s", ENCKEY))
} else if _, in := tMap[SIGKEY]; !in {
return shim.Error(fmt.Sprintf("Expected transient key %s", SIGKEY))
}

return t.EncrypterSigner(stub, args[0], args[1:], tMap[ENCKEY], tMap[SIGKEY])
case "RANGE":
// 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 t.RangeDecrypter(stub, tMap[ENCKEY])
default:
return shim.Error(fmt.Sprintf("Unsupported function %s", f))
}
}

func main() {
factory.InitFactories(nil)

err := shim.Start(&EncCC{factory.GetDefault()})
if err != nil {
fmt.Printf("Error starting EncCC chaincode: %s", err)
}
}
Loading

0 comments on commit e198a5e

Please sign in to comment.