diff --git a/common/crypto/pdo-crypto-c-wrapper.cpp b/common/crypto/pdo-crypto-c-wrapper.cpp index 947e539ac..8361df1d9 100644 --- a/common/crypto/pdo-crypto-c-wrapper.cpp +++ b/common/crypto/pdo-crypto-c-wrapper.cpp @@ -15,6 +15,9 @@ extern "C" { #endif extern "C" const unsigned int SYM_KEY_LEN = pdo::crypto::constants::SYM_KEY_LEN; +extern "C" const unsigned int IV_LEN = pdo::crypto::constants::IV_LEN; +extern "C" const unsigned int TAG_LEN = pdo::crypto::constants::TAG_LEN; + extern "C" const unsigned int RSA_PLAINTEXT_LEN = pdo::crypto::constants::RSA_PLAINTEXT_LEN; extern "C" const unsigned int RSA_KEY_SIZE = pdo::crypto::constants::RSA_KEY_SIZE; @@ -54,9 +57,9 @@ bool verify_signature(uint8_t* public_key, uint32_t public_key_len, uint8_t* mes int r = pk.VerifySignature(msg, sig); COND2ERR(r != 1); } - catch(...) + catch(const std::exception& e) { - COND2ERR(true); + COND2LOGERR(true, e.what()); } // verification successful @@ -66,6 +69,38 @@ bool verify_signature(uint8_t* public_key, uint32_t public_key_len, uint8_t* mes return false; } +bool sign_message(uint8_t* private_key, + uint32_t private_key_len, + uint8_t* message, + uint32_t message_len, + uint8_t* signature, + uint32_t signature_len, + uint32_t* signature_actual_len) +{ + try + { + std::string prk_string((const char*)private_key, private_key_len); + + //deserialize private key + pdo::crypto::sig::PrivateKey prk(prk_string); + + ByteArray ba_signature = prk.SignMessage(ByteArray(message, message + message_len)); + + COND2ERR(ba_signature.size() > signature_len); + memcpy(signature, ba_signature.data(), ba_signature.size()); + *signature_actual_len = ba_signature.size(); + } + catch(const std::exception& e) + { + COND2LOGERR(true, e.what()); + } + + return true; + +err: + return false; +} + bool pk_encrypt_message(uint8_t* public_key, uint32_t public_key_len, uint8_t* message, @@ -86,7 +121,6 @@ bool pk_encrypt_message(uint8_t* public_key, //encrypt message encr_msg = pk.EncryptMessage(msg); - LOG_DEBUG("encr msg size %d buffer len %d", encr_msg.size(), encrypted_message_len); COND2LOGERR(encrypted_message_len < encr_msg.size(), "buffer too small for encrypted msg"); memcpy(encrypted_message, encr_msg.data(), encr_msg.size()); *encrypted_message_actual_len = encr_msg.size(); @@ -103,6 +137,75 @@ bool pk_encrypt_message(uint8_t* public_key, return false; } +bool pk_decrypt_message(uint8_t* private_key, + uint32_t private_key_len, + uint8_t* encrypted_message, + uint32_t encrypted_message_len, + uint8_t* message, + uint32_t message_len, + uint32_t* message_actual_len) +{ + try + { + std::string prk_string((const char*)private_key, private_key_len); + ByteArray encr_msg(encrypted_message, encrypted_message + encrypted_message_len); + ByteArray msg; + + //deserialize private key + pdo::crypto::pkenc::PrivateKey prk(prk_string); + + //decrypt message + msg = prk.DecryptMessage(encr_msg); + + COND2LOGERR(message_len < msg.size(), "buffer too small for decrypted message"); + memcpy(message, msg.data(), msg.size()); + *message_actual_len = msg.size(); + } + catch(const std::exception& e) + { + COND2LOGERR(true, e.what()); + } + + // decryption successful + return true; + +err: + return false; +} + +bool encrypt_message(uint8_t* key, + uint32_t key_len, + uint8_t* message, + uint32_t message_len, + uint8_t* encrypted_message, + uint32_t encrypted_message_len, + uint32_t* encrypted_message_actual_len) +{ + try + { + ByteArray ba_key(key, key + key_len); + ByteArray msg(message, message + message_len); + ByteArray encr_msg; + + //encrypt message + encr_msg = pdo::crypto::skenc::EncryptMessage(ba_key, msg); + + COND2ERR(encrypted_message_len < encr_msg.size()); + memcpy(encrypted_message, encr_msg.data(), encr_msg.size()); + *encrypted_message_actual_len = encr_msg.size(); + } + catch(const std::exception& e) + { + COND2LOGERR(true, e.what()); + } + + //encryption successful + return true; + +err: + return false; +} + bool decrypt_message(uint8_t* key, uint32_t key_len, uint8_t* encrypted_message, @@ -136,6 +239,78 @@ bool decrypt_message(uint8_t* key, return false; } +bool new_rsa_key(uint8_t* public_key, + uint32_t public_key_len, + uint32_t* public_key_actual_len, + uint8_t* private_key, + uint32_t private_key_len, + uint32_t* private_key_actual_len) +{ + try + { + pdo::crypto::pkenc::PrivateKey pri_key; + pdo::crypto::pkenc::PublicKey pub_key; + pri_key.Generate(); + pub_key = pri_key.GetPublicKey(); + + std::string puk = pub_key.Serialize(); + std::string prk = pri_key.Serialize(); + + COND2ERR(puk.length() > public_key_len); + COND2ERR(prk.length() > private_key_len); + + memcpy(public_key, puk.c_str(), puk.length()); + memcpy(private_key, prk.c_str(), prk.length()); + *public_key_actual_len = puk.length(); + *private_key_actual_len = prk.length(); + } + catch(const std::exception& e) + { + COND2LOGERR(true, e.what()); + } + + return true; + +err: + return false; +} + +bool new_ecdsa_key(uint8_t* public_key, + uint32_t public_key_len, + uint32_t* public_key_actual_len, + uint8_t* private_key, + uint32_t private_key_len, + uint32_t* private_key_actual_len) +{ + try + { + pdo::crypto::sig::PrivateKey pri_key; + pdo::crypto::sig::PublicKey pub_key; + pri_key.Generate(); + pub_key = pri_key.GetPublicKey(); + + std::string puk = pub_key.Serialize(); + std::string prk = pri_key.Serialize(); + + COND2ERR(puk.length() > public_key_len); + COND2ERR(prk.length() > private_key_len); + + memcpy(public_key, puk.c_str(), puk.length()); + memcpy(private_key, prk.c_str(), prk.length()); + *public_key_actual_len = puk.length(); + *private_key_actual_len = prk.length(); + } + catch(const std::exception& e) + { + COND2LOGERR(true, e.what()); + } + + return true; + +err: + return false; +} + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/common/crypto/pdo-crypto-c-wrapper.h b/common/crypto/pdo-crypto-c-wrapper.h index a403e23ca..931690d18 100644 --- a/common/crypto/pdo-crypto-c-wrapper.h +++ b/common/crypto/pdo-crypto-c-wrapper.h @@ -11,6 +11,9 @@ extern "C" { #endif extern const unsigned int SYM_KEY_LEN; +extern const unsigned int IV_LEN; +extern const unsigned int TAG_LEN; + extern const unsigned int RSA_PLAINTEXT_LEN; extern const unsigned int RSA_KEY_SIZE; @@ -27,6 +30,14 @@ bool verify_signature(uint8_t* public_key, uint8_t* signature, uint32_t signature_len); +bool sign_message(uint8_t* private_key, + uint32_t private_key_len, + uint8_t* message, + uint32_t message_len, + uint8_t* signature, + uint32_t signature_len, + uint32_t* signature_actual_len); + bool pk_encrypt_message(uint8_t* public_key, uint32_t public_key_len, uint8_t* message, @@ -35,6 +46,22 @@ bool pk_encrypt_message(uint8_t* public_key, uint32_t encrypted_message_len, uint32_t* encrypted_message_actual_len); +bool pk_decrypt_message(uint8_t* private_key, + uint32_t private_key_len, + uint8_t* encrypted_message, + uint32_t encrypted_message_len, + uint8_t* message, + uint32_t message_len, + uint32_t* message_actual_len); + +bool encrypt_message(uint8_t* key, + uint32_t key_len, + uint8_t* message, + uint32_t message_len, + uint8_t* encrypted_message, + uint32_t encrypted_message_len, + uint32_t* encrypted_message_actual_len); + bool decrypt_message(uint8_t* key, uint32_t key_len, uint8_t* encrypted_message, @@ -43,6 +70,20 @@ bool decrypt_message(uint8_t* key, uint32_t message_len, uint32_t* message_actual_len); +bool new_rsa_key(uint8_t* public_key, + uint32_t public_key_len, + uint32_t* public_key_actual_len, + uint8_t* private_key, + uint32_t private_key_len, + uint32_t* private_key_actual_len); + +bool new_ecdsa_key(uint8_t* public_key, + uint32_t public_key_len, + uint32_t* public_key_actual_len, + uint8_t* private_key, + uint32_t private_key_len, + uint32_t* private_key_actual_len); + #ifdef __cplusplus } #endif diff --git a/ecc/chaincode/enclave/mock_enclave.go b/ecc/chaincode/enclave/mock_enclave.go index 332ac3792..bbcdfed0e 100644 --- a/ecc/chaincode/enclave/mock_enclave.go +++ b/ecc/chaincode/enclave/mock_enclave.go @@ -10,12 +10,10 @@ SPDX-License-Identifier: Apache-2.0 package enclave import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/sha256" - "crypto/x509" + "encoding/base64" "encoding/hex" + "fmt" "strings" "github.com/golang/protobuf/proto" @@ -28,8 +26,10 @@ import ( ) type MockEnclaveStub struct { - privateKey *ecdsa.PrivateKey - enclaveId string + privateKey []byte + publicKey []byte + enclaveId string + ccPrivateKey []byte } // NewEnclave starts a new enclave @@ -51,20 +51,23 @@ func (m *MockEnclaveStub) Init(serializedChaincodeParams, serializedHostParamsBy return nil, err } - // create some dummy keys for our mock enclave - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + // create enclave keys + publicKey, privateKey, err := utils.NewECDSAKeys() if err != nil { return nil, err } m.privateKey = privateKey + m.publicKey = publicKey - pubBytes, err := x509.MarshalPKIXPublicKey(privateKey.Public()) + // create chaincode encryption keys keys + ccPublicKey, ccPrivateKey, err := utils.NewRSAKeys() if err != nil { return nil, err } + m.ccPrivateKey = ccPrivateKey - hash := sha256.Sum256(pubBytes) - m.enclaveId = strings.ToUpper(hex.EncodeToString(hash[:])) + // calculate enclave id + m.enclaveId, _ = m.GetEnclaveId() logger.Debug("Init") credentials := &protos.Credentials{ @@ -72,9 +75,10 @@ func (m *MockEnclaveStub) Init(serializedChaincodeParams, serializedHostParamsBy SerializedAttestedData: &any.Any{ TypeUrl: proto.MessageName(&protos.AttestedData{}), Value: protoutil.MarshalOrPanic(&protos.AttestedData{ - EnclaveVk: pubBytes, - CcParams: chaincodeParams, - HostParams: hostParams, + EnclaveVk: publicKey, + CcParams: chaincodeParams, + HostParams: hostParams, + ChaincodeEk: ccPublicKey, }), }, } @@ -99,11 +103,7 @@ func (m MockEnclaveStub) ImportCCKeys() ([]byte, error) { } func (m *MockEnclaveStub) GetEnclaveId() (string, error) { - pubBytes, err := x509.MarshalPKIXPublicKey(m.privateKey.Public()) - if err != nil { - return "", err - } - hash := sha256.Sum256(pubBytes) + hash := sha256.Sum256(m.publicKey) return strings.ToUpper(hex.EncodeToString(hash[:])), nil } @@ -115,7 +115,38 @@ func (m *MockEnclaveStub) ChaincodeInvoke(stub shim.ChaincodeStubInterface, chai shim.Error(err.Error()) } + // unmarshal request + chaincodeRequestMessageProto := &protos.ChaincodeRequestMessage{} + err = proto.Unmarshal(chaincodeRequestMessage, chaincodeRequestMessageProto) + if err != nil { + return nil, err + } + + encryptedRequestBytes := chaincodeRequestMessageProto.GetEncryptedRequest() + if encryptedRequestBytes == nil { + return nil, fmt.Errorf("no encrypted request") + } + + // decrypt request + requestBytes, err := utils.PkDecryptMessage(m.ccPrivateKey, encryptedRequestBytes) + if err != nil { + return nil, err + } + + requestProto := &protos.CleartextChaincodeRequest{} + err = proto.Unmarshal(requestBytes, requestProto) + if err != nil { + return nil, err + } + + // get return encryption key + returnEncryptionKey := requestProto.GetReturnEncryptionKey() + if returnEncryptionKey == nil { + return nil, fmt.Errorf("no return encryption key") + } + v, _ := stub.GetState("SomeOtherKey") + v_hash := sha256.Sum256(v) logger.Debug("get state: %s", v) rwset := &kvrwset.KVRWSet{ @@ -130,28 +161,50 @@ func (m *MockEnclaveStub) ChaincodeInvoke(stub shim.ChaincodeStubInterface, chai }}, } + readValueHashes := [][]byte{v_hash[:]} + + fpcKvSet := &protos.FPCKVSet{ + RwSet: rwset, + ReadValueHashes: readValueHashes, + } + + requestMessageHash := sha256.Sum256(chaincodeRequestMessage) + + //create dummy response + responseData := []byte("some response") + + //response must be encoded + b64ResponseData := base64.StdEncoding.EncodeToString(responseData) + + //encrypt response + encryptedResponse, err := utils.EncryptMessage(returnEncryptionKey, []byte(b64ResponseData)) + if err != nil { + return nil, err + } + response := &protos.ChaincodeResponseMessage{ - EncryptedResponse: []byte("some response"), - RwSet: rwset, - Signature: nil, - EnclaveId: m.enclaveId, - Proposal: signedProposal, + EncryptedResponse: encryptedResponse, + FpcRwSet: fpcKvSet, + EnclaveId: m.enclaveId, + Proposal: signedProposal, + ChaincodeRequestMessageHash: requestMessageHash[:], } - // get the read/write set in the same format as processed by the chaincode enclaves - readset, writeset, err := utils.ReplayReadWrites(stub, response.RwSet) + responseBytes, err := proto.Marshal(response) if err != nil { - shim.Error(err.Error()) + return nil, err } // create signature - hash := utils.ComputedHash(response, readset, writeset) - sig, err := ecdsa.SignASN1(rand.Reader, m.privateKey, hash[:]) + sig, err := utils.SignMessage(m.privateKey, responseBytes) if err != nil { return nil, err } - response.Signature = sig + signedResponse := &protos.SignedChaincodeResponseMessage{ + ChaincodeResponseMessage: responseBytes, + Signature: sig, + } - return proto.Marshal(response) + return proto.Marshal(signedResponse) } diff --git a/internal/utils/crypto.go b/internal/utils/crypto.go index a68ac4042..01fdc036f 100644 --- a/internal/utils/crypto.go +++ b/internal/utils/crypto.go @@ -8,28 +8,166 @@ SPDX-License-Identifier: Apache-2.0 package utils import ( - "crypto/sha256" - - "github.com/hyperledger-labs/fabric-private-chaincode/internal/protos" + "fmt" ) -func ComputedHash(responseMsg *protos.ChaincodeResponseMessage, readset, writeset [][]byte) [32]byte { - // H(proposal_payload || proposal_signature || response || read set || write set) - - // TODO add missing delimiters or use length encoding; - // this also needs to be in sync with ecc_enclave/enclave/enclave.cpp - // https://github.com/hyperledger-labs/fabric-private-chaincode/blob/master/ecc_enclave/enclave/enclave.cpp#L85 - h := sha256.New() - h.Write(responseMsg.Proposal.ProposalBytes) - h.Write(responseMsg.Proposal.Signature) - h.Write(responseMsg.EncryptedResponse) - for _, r := range readset { - h.Write(r) +// #cgo CFLAGS: -I${SRCDIR}/../../common/crypto +// #cgo LDFLAGS: -L${SRCDIR}/../../common/crypto/_build -L${SRCDIR}/../../common/logging/_build -Wl,--start-group -lupdo-crypto-adapt -lupdo-crypto -Wl,--end-group -lcrypto -lulogging -lstdc++ +// #include +// #include +// #include +// #include +// #include "pdo-crypto-c-wrapper.h" +import "C" + +func NewRSAKeys() (publicKey []byte, privateKey []byte, e error) { + //The RSA keys created by the PDO crypto lib are 2048bit long and PEM encoded + //Here we roughly estimate that they fit in 2KB + const estimatedPemRsaLen = 2048 + const serializedPublicKeyLen = estimatedPemRsaLen + serializedPublicKeyPtr := C.malloc(serializedPublicKeyLen) + defer C.free(serializedPublicKeyPtr) + serializedPublicKeyActualLen := C.uint32_t(0) + + const serializedPrivateKeyLen = estimatedPemRsaLen + serializedPrivateKeyPtr := C.malloc(serializedPrivateKeyLen) + defer C.free(serializedPrivateKeyPtr) + serializedPrivateKeyActualLen := C.uint32_t(0) + + ret := C.new_rsa_key( + (*C.uint8_t)(serializedPublicKeyPtr), + serializedPublicKeyLen, + &serializedPublicKeyActualLen, + (*C.uint8_t)(serializedPrivateKeyPtr), + serializedPrivateKeyLen, + &serializedPrivateKeyActualLen, + ) + if ret == false { + return nil, nil, fmt.Errorf("cannot create RSA keys") + } + + return C.GoBytes(serializedPublicKeyPtr, C.int(serializedPublicKeyActualLen)), C.GoBytes(serializedPrivateKeyPtr, C.int(serializedPrivateKeyActualLen)), nil +} + +func NewECDSAKeys() (publicKey []byte, privateKey []byte, e error) { + //The ECDSA keys created by the PDO crypto lib are PEM encoded + //Here we roughly estimate that they fit in 2KB + const estimatedPemEcdsaLen = 2048 + const serializedPublicKeyLen = estimatedPemEcdsaLen + serializedPublicKeyPtr := C.malloc(serializedPublicKeyLen) + defer C.free(serializedPublicKeyPtr) + serializedPublicKeyActualLen := C.uint32_t(0) + + const serializedPrivateKeyLen = estimatedPemEcdsaLen + serializedPrivateKeyPtr := C.malloc(serializedPrivateKeyLen) + defer C.free(serializedPrivateKeyPtr) + serializedPrivateKeyActualLen := C.uint32_t(0) + + ret := C.new_ecdsa_key( + (*C.uint8_t)(serializedPublicKeyPtr), + serializedPublicKeyLen, + &serializedPublicKeyActualLen, + (*C.uint8_t)(serializedPrivateKeyPtr), + serializedPrivateKeyLen, + &serializedPrivateKeyActualLen, + ) + if ret == false { + return nil, nil, fmt.Errorf("cannot create ECDSA keys") + } + + return C.GoBytes(serializedPublicKeyPtr, C.int(serializedPublicKeyActualLen)), C.GoBytes(serializedPrivateKeyPtr, C.int(serializedPrivateKeyActualLen)), nil +} + +func SignMessage(privateKey []byte, message []byte) (signature []byte, e error) { + privateKeyPtr := C.CBytes(privateKey) + defer C.free(privateKeyPtr) + + messagePtr := C.CBytes(message) + defer C.free(messagePtr) + + estimatedSignatureLen := C.RSA_KEY_SIZE >> 3 //bits-to-bytes conversion + signaturePtr := C.malloc(C.ulong(estimatedSignatureLen)) + defer C.free(signaturePtr) + signatureActualLen := C.uint32_t(0) + + ret := C.sign_message( + (*C.uint8_t)(privateKeyPtr), + (C.uint32_t)(len(privateKey)), + (*C.uint8_t)(messagePtr), + (C.uint32_t)(len(message)), + (*C.uint8_t)(signaturePtr), + (C.uint32_t)(estimatedSignatureLen), + &signatureActualLen) + if ret == false { + return nil, fmt.Errorf("cannot sign message") } - for _, w := range writeset { - h.Write(w) + + return C.GoBytes(signaturePtr, C.int(signatureActualLen)), nil +} + +func PkDecryptMessage(privateKey []byte, encryptedMessage []byte) (message []byte, e error) { + //This is an RSA dencryption performed with the pdo crypto library + //Importantly, the library uses 2048bit RSA keys & OAEP encoding, so the input size can be at most ~200bytes + //TODO-1: bump up the key length to 3072 to match NIST strength + //TODO-2: extend procedure for large input sizes (via hybrid encryption) + + privateKeyPtr := C.CBytes(privateKey) + defer C.free(privateKeyPtr) + + encryptedMessagePtr := C.CBytes(encryptedMessage) + defer C.free(encryptedMessagePtr) + + //estimate that the decrypted message will not be larger than the encrypted one + decryptedMessageLen := len(encryptedMessage) + decryptedMessagePtr := C.malloc(C.ulong(decryptedMessageLen)) + defer C.free(decryptedMessagePtr) + + decryptedMessageActualLen := C.uint32_t(0) + + ret := C.pk_decrypt_message( + (*C.uint8_t)(privateKeyPtr), + (C.uint32_t)(len(privateKey)), + (*C.uint8_t)(encryptedMessagePtr), + (C.uint32_t)(len(encryptedMessage)), + (*C.uint8_t)(decryptedMessagePtr), + (C.uint32_t)(decryptedMessageLen), + &decryptedMessageActualLen, + ) + if ret == false { + return nil, fmt.Errorf("pk decryption failed") + } + + return C.GoBytes(decryptedMessagePtr, C.int(decryptedMessageActualLen)), nil +} + +func EncryptMessage(key []byte, message []byte) (encryptedMessage []byte, e error) { + //This is a symmetric-key encryption performed with the PDO crypto lib + + keyPtr := C.CBytes(key) + defer C.free(keyPtr) + + messagePtr := C.CBytes(message) + defer C.free(messagePtr) + + //The PDO lib includes the iv and tag in the encrypted message + encryptedMessageLen := C.uint32_t(len(message)) + C.IV_LEN + C.TAG_LEN + encryptedMessagePtr := C.malloc(C.ulong(encryptedMessageLen)) + defer C.free(encryptedMessagePtr) + + encryptedMessageActualLen := C.uint32_t(0) + + ret := C.encrypt_message( + (*C.uint8_t)(keyPtr), + (C.uint32_t)(len(key)), + (*C.uint8_t)(messagePtr), + (C.uint32_t)(len(message)), + (*C.uint8_t)(encryptedMessagePtr), + encryptedMessageLen, + &encryptedMessageActualLen, + ) + if ret == false { + return nil, fmt.Errorf("encryption failed") } - // hash again!!! Note that, sgx_sign() takes the hash, as computed above, as input and hashes again - return sha256.Sum256(h.Sum(nil)) + return C.GoBytes(encryptedMessagePtr, C.int(encryptedMessageActualLen)), nil }