Skip to content

Commit

Permalink
Add servers' signature verification.
Browse files Browse the repository at this point in the history
Resolves #23 by adding method to verify signatures comming with servers'
responses.

Signed-off-by: Artem Barger <[email protected]>
Signed-off-by: Yoav Tock <[email protected]>
  • Loading branch information
C0rWin authored and tock-ibm committed Jun 29, 2021
1 parent 2e0c03f commit 3fa5668
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 61 deletions.
28 changes: 21 additions & 7 deletions pkg/bcdb/config_tx_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,13 +413,14 @@ func TestConfigTxContext_DeleteClusterNode(t *testing.T) {
config, err := tx1.GetClusterConfig()
require.NoError(t, err)

id1 := config.Nodes[0].ID
node1 := config.Nodes[0]
node2 := &types.NodeConfig{
ID: "testNode2",
Address: config.Nodes[0].Address,
Port: config.Nodes[0].Port + 1,
Certificate: config.Nodes[0].Certificate,
}
peer1 := config.ConsensusConfig.Members[0]
peer2 := &types.PeerConfig{
NodeId: "testNode2",
RaftId: config.ConsensusConfig.Members[0].RaftId + 1,
Expand All @@ -435,9 +436,22 @@ func TestConfigTxContext_DeleteClusterNode(t *testing.T) {
require.NotNil(t, receipt)
require.Equal(t, types.Flag_VALID, receipt.Header.ValidationInfo[receipt.GetTxIndex()].Flag)


tx2, err := session1.ConfigTx()
require.NoError(t, err)
err = tx2.DeleteClusterNode(id1)

clusterConfig, err := tx2.GetClusterConfig()
require.NoError(t, err)
require.NotNil(t, clusterConfig)
require.Len(t, clusterConfig.Nodes, 2)
found, index := NodeExists("testNode2", clusterConfig.Nodes)
require.True(t, found)
require.Equal(t, clusterConfig.Nodes[index].Port, node2.Port)
found, index = PeerExists("testNode2", clusterConfig.ConsensusConfig.Members)
require.True(t, found)
require.Equal(t, clusterConfig.ConsensusConfig.Members[index].PeerPort, peer2.PeerPort)

err = tx2.DeleteClusterNode(node2.ID)
require.NoError(t, err)

txID, receipt, err = tx2.Commit(true)
Expand All @@ -449,17 +463,17 @@ func TestConfigTxContext_DeleteClusterNode(t *testing.T) {
// verify tx was successfully committed. "Get" works once per Tx.
tx3, err := session1.ConfigTx()
require.NoError(t, err)
clusterConfig, err := tx3.GetClusterConfig()
clusterConfig, err = tx3.GetClusterConfig()
require.NoError(t, err)
require.NotNil(t, clusterConfig)
require.Len(t, clusterConfig.Nodes, 1)

found, index := NodeExists("testNode2", clusterConfig.Nodes)
found, index = NodeExists("testNode1", clusterConfig.Nodes)
require.True(t, found)
require.Equal(t, clusterConfig.Nodes[index].Port, node2.Port)
found, index = PeerExists("testNode2", clusterConfig.ConsensusConfig.Members)
require.Equal(t, clusterConfig.Nodes[index].Port, node1.Port)
found, index = PeerExists("testNode1", clusterConfig.ConsensusConfig.Members)
require.True(t, found)
require.Equal(t, clusterConfig.ConsensusConfig.Members[index].PeerPort, peer2.PeerPort)
require.Equal(t, clusterConfig.ConsensusConfig.Members[index].PeerPort, peer1.PeerPort)
}

//TODO this test will stop working once we implement quorum rules
Expand Down
80 changes: 43 additions & 37 deletions pkg/bcdb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"time"

"github.com/IBM-Blockchain/bcdb-sdk/pkg/config"
"github.com/IBM-Blockchain/bcdb-server/pkg/certificateauthority"
"github.com/IBM-Blockchain/bcdb-server/pkg/constants"
"github.com/IBM-Blockchain/bcdb-server/pkg/crypto"
"github.com/IBM-Blockchain/bcdb-server/pkg/cryptoservice"
Expand Down Expand Up @@ -119,28 +120,26 @@ func Create(config *config.ConnectionConfig) (BCDB, error) {
}
}

// Load root CA certificates
certsPool := x509.NewCertPool()
var rootCAs [][]byte
for _, rootCAPath := range config.RootCAs {
rootCABytes, err := ioutil.ReadFile(rootCAPath)
if err != nil {
dbLogger.Errorf("failed to read root CA certificate, due to %s", err)
return nil, errors.Wrap(err, "failed to read root CA certificate")
}
// TODO there are might be multiple PEM encoded blocks need to make
// sure we read correct one
pemBlock, _ := pem.Decode(rootCABytes)
if pemBlock == nil {
dbLogger.Error("failed decoding root CA certificate")
return nil, errors.New("failed decoding root CA certificate")
}
rootCACert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
dbLogger.Errorf("failed to parse X509 root CA certificate, due to %s", err)
return nil, errors.Wrap(err, "failed to parse X509 root CA certificate")
}
certsPool.AddCert(rootCACert)
asn1Data, _ := pem.Decode(rootCABytes)
rootCAs = append(rootCAs, asn1Data.Bytes)
}
rootCACerts, err := certificateauthority.NewCACertCollection(rootCAs, nil)
if err != nil {
dbLogger.Errorf("failed to create CACertCollection, due to %s", err)
return nil, err
}
if err = rootCACerts.VerifyCollection(); err != nil {
dbLogger.Errorf("verification of CA certs collection is failed, due to %s", err)
return nil, err
}

// Validate replica set URIs
urls := map[string]*url.URL{}
for _, uri := range config.ReplicaSet {
Expand All @@ -154,14 +153,14 @@ func Create(config *config.ConnectionConfig) (BCDB, error) {

return &bDB{
replicaSet: urls,
rootCAs: certsPool,
rootCAs: rootCACerts,
logger: dbLogger,
}, nil
}

type bDB struct {
replicaSet map[string]*url.URL
rootCAs *x509.CertPool
rootCAs *certificateauthority.CACertCollection
logger *logger.SugarLogger
}

Expand Down Expand Up @@ -201,13 +200,13 @@ type dbSession struct {
signer Signer
userCert []byte
replicaSet map[string]*url.URL
rootCAs *x509.CertPool
rootCAs *certificateauthority.CACertCollection
txTimeout time.Duration
queryTimeout time.Duration
logger *logger.SugarLogger
}

func (d *dbSession) getNodesCerts(replica *url.URL, httpClient *http.Client) (map[string]*x509.Certificate, error) {
func (d *dbSession) getNodesCerts(replica *url.URL, httpClient *http.Client) (SignatureVerifier, error) {
nodesCerts := map[string]*x509.Certificate{}
getConfig := &url.URL{
Path: constants.URLForGetConfig(),
Expand Down Expand Up @@ -254,10 +253,6 @@ func (d *dbSession) getNodesCerts(replica *url.URL, httpClient *http.Client) (ma
return nil, err
}

// TODO need to validate payload's signature
// resEnv.Signature - the signature over payload
// payload.GetHeader().NodeID - the id of the node signed response

configResponse := &types.GetConfigResponse{}
err = json.Unmarshal(payload.GetResponse(), configResponse)
if err != nil {
Expand All @@ -266,22 +261,31 @@ func (d *dbSession) getNodesCerts(replica *url.URL, httpClient *http.Client) (ma
}

for _, node := range configResponse.GetConfig().GetNodes() {
cert, err := x509.ParseCertificate(node.Certificate)
err := d.rootCAs.VerifyLeafCert(node.Certificate)
if err != nil {
return nil, err
}

_, err = cert.Verify(x509.VerifyOptions{
Roots: d.rootCAs,
})
cert, err := x509.ParseCertificate(node.Certificate)
if err != nil {
return nil, err
}

nodesCerts[node.ID] = cert
}

return nodesCerts, nil
verifier, err := NewVerifier(nodesCerts, d.logger)
if err != nil {
return nil, err
}

if err = verifier.Verify(
payload.GetHeader().GetNodeID(),
resEnv.GetPayload(),
resEnv.GetSignature()); err != nil {
d.logger.Errorf("failed to verify configuration response, error = %s", err)
return nil, errors.Errorf("failed to verify configuration response, error = %s", err)
}

return verifier, err
}

// UsersTx returns user's transaction context
Expand Down Expand Up @@ -368,16 +372,18 @@ func (d *dbSession) Ledger() (Ledger, error) {
func (d *dbSession) newCommonTxContext() (*commonTxContext, error) {
httpClient := d.newHTTPClient()

nodesCerts, err := d.getServerCertificates(httpClient)
verifier, err := d.sigVerifier(httpClient)

if err != nil {
return nil, err
}

commonTxContext := &commonTxContext{
userID: d.userID,
signer: d.signer,
userCert: d.userCert,
replicaSet: d.replicaSet,
nodesCerts: nodesCerts,
verifier: verifier,
restClient: NewRestClient(d.userID, httpClient, d.signer),
commitTimeout: d.txTimeout,
queryTimeout: d.queryTimeout,
Expand All @@ -386,22 +392,22 @@ func (d *dbSession) newCommonTxContext() (*commonTxContext, error) {
return commonTxContext, nil
}

func (d *dbSession) getServerCertificates(httpClient *http.Client) (map[string]*x509.Certificate, error) {
var nodesCerts map[string]*x509.Certificate
func (d *dbSession) sigVerifier(httpClient *http.Client) (SignatureVerifier, error) {
var verifier SignatureVerifier
var err error
for _, replica := range d.replicaSet {
nodesCerts, err = d.getNodesCerts(replica, httpClient)
verifier, err = d.getNodesCerts(replica, httpClient)
if err != nil {
d.logger.Errorf("failed to obtain server's certificate, replica: %s", replica)
continue
}
}

if len(nodesCerts) == 0 {
if verifier == nil {
d.logger.Errorf("failed to obtain server's certificate, replicaSet: %s", d.replicaSet)
return nil, errors.New("failed to obtain server's certificate")
}
return nodesCerts, nil
return verifier, nil
}

func (d *dbSession) newHTTPClient() *http.Client {
Expand Down
15 changes: 10 additions & 5 deletions pkg/bcdb/mocks/rest_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions pkg/bcdb/mocks/signature_verifier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 14 additions & 9 deletions pkg/bcdb/tx_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package bcdb

import (
"context"
"crypto/x509"
"encoding/json"
"net/http"
"net/url"
Expand All @@ -25,7 +24,7 @@ type commonTxContext struct {
signer Signer
userCert []byte
replicaSet map[string]*url.URL
nodesCerts map[string]*x509.Certificate
verifier SignatureVerifier
restClient RestClient
txEnvelope proto.Message
commitTimeout time.Duration
Expand Down Expand Up @@ -108,17 +107,20 @@ func (t *commonTxContext) commit(tx txContext, postEndpoint string, sync bool) (
return txID, nil, err
}

nodeID := payload.GetHeader().GetNodeID()
err = t.verifier.Verify(nodeID, txResponseEnvelope.GetPayload(), txResponseEnvelope.GetSignature())
if err != nil {
t.logger.Errorf("signature verification failed nodeID %s, due to %s", nodeID, err)
return "", nil, errors.Errorf("signature verification failed nodeID %s, due to %s", nodeID, err)
}

txResponse := &types.TxResponse{}
err = json.Unmarshal(payload.GetResponse(), txResponse)
if err != nil {
t.logger.Errorf("failed to unmarshal response, due to %s", err)
return txID, nil, err
}

// TODO need to validate payload's signature
// r.Signature - the signature over payload
// payload.GetHeader().NodeID - the id of the node signed response

t.txSpent = true
tx.cleanCtx()
return txID, txResponse.GetReceipt(), nil
Expand Down Expand Up @@ -187,9 +189,12 @@ func (t *commonTxContext) handleRequest(rawurl string, query, res proto.Message)
return err
}

// TODO need to validate payload's signature
// r.Signature - the signature over payload
// payload.GetHeader().NodeID - the id of the node signed response
nodeID := payload.GetHeader().GetNodeID()
err = t.verifier.Verify(nodeID, r.GetPayload(), r.GetSignature())
if err != nil {
t.logger.Errorf("signature verification failed nodeID %s, due to %s", nodeID, err)
return errors.Errorf("signature verification failed nodeID %s, due to %s", nodeID, err)
}

err = json.Unmarshal(payload.GetResponse(), res)
if err != nil {
Expand Down
Loading

0 comments on commit 3fa5668

Please sign in to comment.