Skip to content

Commit c698163

Browse files
committed
Verify signed transactions
Signed-off-by: Genady Gurevich <[email protected]>
1 parent 199b8cc commit c698163

File tree

7 files changed

+182
-28
lines changed

7 files changed

+182
-28
lines changed

common/tools/armageddon/armageddon.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ func generateConfigAndCrypto(genConfigFile **os.File, outputDir *string, sampleC
352352
userTLSPrivateKeyPath := filepath.Join(*outputDir, "crypto", "ordererOrganizations", fmt.Sprintf("org%d", i+1), "users", "user", "tls", "user-key.pem")
353353
userTLSCertPath := filepath.Join(*outputDir, "crypto", "ordererOrganizations", fmt.Sprintf("org%d", i+1), "users", "user", "tls", "user-tls-cert.pem")
354354

355-
userConfig, err := NewUserConfig(userTLSPrivateKeyPath, userTLSCertPath, tlsCACertsBytesPartiesCollection, networkConfig)
355+
userConfig, err := NewUserConfig(*outputDir, userTLSPrivateKeyPath, userTLSCertPath, tlsCACertsBytesPartiesCollection, networkConfig)
356356
if err != nil {
357357
fmt.Fprintf(os.Stderr, "Error creating user config: %s", err)
358358
os.Exit(-1)
@@ -596,9 +596,9 @@ func sendTxToRouters(userConfig *UserConfig, numOfTxs int, rate int, txSize int,
596596
var streams []ab.AtomicBroadcast_BroadcastClient
597597

598598
// create gRPC clients and streams to the routers
599-
for i := 0; i < len(userConfig.RouterEndpoints); i++ {
599+
for i := 0; i < len(userConfig.RouterUserConfigs); i++ {
600600
// create a gRPC connection to the router
601-
gRPCRouterClientConn, stream, err := createConnAndStream(userConfig, userConfig.RouterEndpoints[i])
601+
gRPCRouterClientConn, stream, err := createConnAndStream(userConfig, userConfig.RouterUserConfigs[i].Endpoint)
602602
if err != nil {
603603
fmt.Fprintf(os.Stderr, "failed to create a gRPC client connection and stream to router %d, err: %v", i+1, err)
604604
os.Exit(3)

common/tools/armageddon/broadcast_client.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,14 @@ type BroadcastTxClient struct {
128128
func NewBroadcastTxClient(userConfigFile *UserConfig) *BroadcastTxClient {
129129
return &BroadcastTxClient{
130130
userConfig: userConfigFile,
131-
streamsToRouters: make([]*StreamInfo, len(userConfigFile.RouterEndpoints)),
131+
streamsToRouters: make([]*StreamInfo, len(userConfigFile.RouterUserConfigs)),
132132
stopChan: make(chan struct{}),
133133
}
134134
}
135135

136136
func (c *BroadcastTxClient) InitStreams() error {
137-
for i, routerEndpoint := range c.userConfig.RouterEndpoints {
138-
conn, stream, err := createConnAndStream(c.userConfig, routerEndpoint)
137+
for i, routerUserConfig := range c.userConfig.RouterUserConfigs {
138+
conn, stream, err := createConnAndStream(c.userConfig, routerUserConfig.Endpoint)
139139
if err != nil {
140140
return err
141141
}
@@ -145,7 +145,7 @@ func (c *BroadcastTxClient) InitStreams() error {
145145
stream: stream,
146146
stopChan: make(chan struct{}),
147147
maxRetryDelay: 8 * time.Second,
148-
endpoint: routerEndpoint,
148+
endpoint: routerUserConfig.Endpoint,
149149
lock: sync.Mutex{},
150150
logger: flogging.MustGetLogger(fmt.Sprintf("BroadcastClientToRouter%d", i+1)),
151151
}

common/tools/armageddon/user.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,41 @@ package armageddon
88

99
import (
1010
"fmt"
11+
"path/filepath"
1112

13+
"github.com/hyperledger/fabric-x-orderer/common/types"
1214
"github.com/hyperledger/fabric-x-orderer/common/utils"
1315
genconfig "github.com/hyperledger/fabric-x-orderer/config/generate"
1416
)
1517

18+
type RouterUserConfig struct {
19+
Endpoint string `yaml:"Endpoint,omitempty"`
20+
PartyID types.PartyID `yaml:"PartyID,omitempty"`
21+
UserMSPDir string `yaml:"UserMSPDir,omitempty"`
22+
}
23+
1624
// UserConfig holds the user information needed for connection to routers and assemblers
1725
// Note: a user will be created for each party. One of the users will be chosen as a grpc client that sends tx to all router and receives blocks from the assemblers.
1826
type UserConfig struct {
19-
TLSPrivateKey []byte `yaml:"TLSPrivateKey,omitempty"`
20-
TLSCertificate []byte `yaml:"TLSCertificate,omitempty"`
21-
RouterEndpoints []string `yaml:"RouterEndpoints,omitempty"`
22-
AssemblerEndpoints []string `yaml:"AssemblerEndpoints,omitempty"`
23-
TLSCACerts [][]byte `yaml:"TLSCACerts,omitempty"`
24-
UseTLSRouter string `yaml:"UseTLSRouter,omitempty"`
25-
UseTLSAssembler string `yaml:"UseTLSAssembler,omitempty"`
27+
TLSPrivateKey []byte `yaml:"TLSPrivateKey,omitempty"`
28+
TLSCertificate []byte `yaml:"TLSCertificate,omitempty"`
29+
RouterUserConfigs []RouterUserConfig `yaml:"RouterUserConfigs,omitempty"`
30+
AssemblerEndpoints []string `yaml:"AssemblerEndpoints,omitempty"`
31+
TLSCACerts [][]byte `yaml:"TLSCACerts,omitempty"`
32+
UseTLSRouter string `yaml:"UseTLSRouter,omitempty"`
33+
UseTLSAssembler string `yaml:"UseTLSAssembler,omitempty"`
2634
}
2735

28-
func NewUserConfig(privateKeyPath string, tlsCertPath string, tlsCACerts [][]byte, network *genconfig.Network) (*UserConfig, error) {
36+
func NewUserConfig(outputDir string, privateKeyPath string, tlsCertPath string, tlsCACerts [][]byte, network *genconfig.Network) (*UserConfig, error) {
2937
// collect router and assembler endpoints, required for defining a user
30-
var routerEndpoints []string
38+
var routerUserConfigs []RouterUserConfig
3139
var assemblerEndpoints []string
3240
for _, party := range network.Parties {
33-
routerEndpoints = append(routerEndpoints, party.RouterEndpoint)
41+
routerUserConfigs = append(routerUserConfigs, RouterUserConfig{
42+
Endpoint: party.RouterEndpoint,
43+
PartyID: party.ID,
44+
UserMSPDir: filepath.Join(outputDir, fmt.Sprintf("crypto/ordererOrganizations/org%d/users/user/msp", party.ID)),
45+
})
3446
assemblerEndpoints = append(assemblerEndpoints, party.AssemblerEndpoint)
3547
}
3648

@@ -47,7 +59,7 @@ func NewUserConfig(privateKeyPath string, tlsCertPath string, tlsCACerts [][]byt
4759
return &UserConfig{
4860
TLSPrivateKey: privateKey,
4961
TLSCertificate: tlsCert,
50-
RouterEndpoints: routerEndpoints,
62+
RouterUserConfigs: routerUserConfigs,
5163
AssemblerEndpoints: assemblerEndpoints,
5264
TLSCACerts: tlsCACerts,
5365
UseTLSRouter: network.UseTLSRouter,

test/router_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616

1717
"github.com/hyperledger/fabric-x-orderer/common/tools/armageddon"
1818
"github.com/hyperledger/fabric-x-orderer/common/types"
19+
"github.com/hyperledger/fabric-x-orderer/common/utils"
20+
config "github.com/hyperledger/fabric-x-orderer/config"
1921
"github.com/hyperledger/fabric-x-orderer/testutil"
2022
"github.com/hyperledger/fabric-x-orderer/testutil/client"
2123
"github.com/hyperledger/fabric-x-orderer/testutil/tx"
@@ -350,3 +352,81 @@ func TestSubmitToRouterGetMetrics(t *testing.T) {
350352
return testutil.RouterIncomingTxMetric(t, types.PartyID(1), url) == totalTxNumber
351353
}, 30*time.Second, 100*time.Millisecond)
352354
}
355+
356+
// TestVerifySignedTxsByRouterSingleParty verifies that a router running in a single-party,
357+
// single-shard configuration correctly enforces client signature verification when accepting
358+
// and broadcasting transactions.
359+
func TestVerifySignedTxsByRouterSingleParty(t *testing.T) {
360+
// 1. compile arma
361+
armaBinaryPath, err := gexec.BuildWithEnvironment("github.com/hyperledger/fabric-x-orderer/cmd/arma", []string{"GOPRIVATE=" + os.Getenv("GOPRIVATE")})
362+
defer gexec.CleanupBuildArtifacts()
363+
require.NoError(t, err)
364+
require.NotNil(t, armaBinaryPath)
365+
366+
t.Logf("Running test with %d parties and %d shards", 1, 1)
367+
368+
// 2. Create a temporary directory for the test.
369+
dir, err := os.MkdirTemp("", t.Name())
370+
require.NoError(t, err)
371+
defer os.RemoveAll(dir)
372+
373+
// 3. Create a config YAML file in the temporary directory.
374+
configPath := filepath.Join(dir, "config.yaml")
375+
netInfo := testutil.CreateNetwork(t, configPath, 1, 1, "none", "none")
376+
require.NoError(t, err)
377+
numOfArmaNodes := len(netInfo)
378+
379+
// 4. Generate the config files in the temporary directory using the armageddon generate command.
380+
armageddon.NewCLI().Run([]string{"generate", "--config", configPath, "--output", dir})
381+
382+
configFilePath := filepath.Join(dir, fmt.Sprintf("config/party%d/local_config_router.yaml", types.PartyID(1)))
383+
conf, _, err := config.LoadLocalConfig(configFilePath)
384+
require.NoError(t, err)
385+
386+
// Modify the router configuration to require client signature verification.
387+
conf.NodeLocalConfig.GeneralConfig.ClientSignatureVerificationRequired = true
388+
utils.WriteToYAML(conf.NodeLocalConfig, configFilePath)
389+
390+
conf, _, err = config.LoadLocalConfig(configFilePath)
391+
require.NoError(t, err)
392+
require.True(t, conf.NodeLocalConfig.GeneralConfig.ClientSignatureVerificationRequired)
393+
394+
// 5. Run the arma nodes.
395+
// NOTE: if one of the nodes is not started within 10 seconds, there is no point in continuing the test, so fail it
396+
// Obtains a test user configuration and constructs a broadcast client.
397+
readyChan := make(chan struct{}, numOfArmaNodes)
398+
armaNetwork := testutil.RunArmaNodes(t, dir, armaBinaryPath, readyChan, netInfo)
399+
defer armaNetwork.Stop()
400+
401+
testutil.WaitReady(t, readyChan, numOfArmaNodes, 10)
402+
403+
uc, err := testutil.GetUserConfig(dir, 1)
404+
assert.NoError(t, err)
405+
assert.NotNil(t, uc)
406+
407+
totalTxNumber := 1000
408+
// rate limiter parameters
409+
fillInterval := 10 * time.Millisecond
410+
fillFrequency := 1000 / int(fillInterval.Milliseconds())
411+
rate := 500
412+
413+
capacity := rate / fillFrequency
414+
rl, err := armageddon.NewRateLimiter(rate, fillInterval, capacity)
415+
if err != nil {
416+
fmt.Fprintf(os.Stderr, "failed to start a rate limiter")
417+
os.Exit(3)
418+
}
419+
420+
broadcastClient := client.NewBroadcastTxClient(uc, 10*time.Second)
421+
422+
for i := range totalTxNumber {
423+
status := rl.GetToken()
424+
if !status {
425+
fmt.Fprintf(os.Stderr, "failed to send tx %d", i+1)
426+
os.Exit(3)
427+
}
428+
txContent := tx.PrepareTxWithTimestamp(i, 64, []byte("sessionNumber"))
429+
err = broadcastClient.SendTx(txContent)
430+
require.NoError(t, err)
431+
}
432+
}

test/tx_client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ func TestTxClientSend(t *testing.T) {
7777
handler := func(block *common.Block) error {
7878
totalTxs += len(block.Data.Data)
7979
totalBlocks++
80-
if totalTxs == totalTxNumber+1 {
80+
if totalBlocks >= int(endBlock-startBlock)+1 {
8181
cancel()
8282
return context.Canceled
8383
}
8484
return nil
8585
}
8686
dc.PullBlocks(cnx, 1, startBlock, endBlock, handler)
87-
assert.Equal(t, totalTxNumber+1, totalTxs)
87+
assert.GreaterOrEqual(t, totalBlocks, int(endBlock-startBlock)+1)
8888
assert.True(t, totalBlocks > 2)
8989
t.Logf("Finished pull and count: %d, %d", totalBlocks, totalTxs)
9090
}

testutil/client/tx_broadcast_client.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"context"
1111
"fmt"
1212
"io"
13+
"os"
14+
"path/filepath"
1315
"strings"
1416
"sync"
1517
"time"
@@ -18,7 +20,9 @@ import (
1820
"github.com/hyperledger/fabric-protos-go-apiv2/common"
1921
ab "github.com/hyperledger/fabric-protos-go-apiv2/orderer"
2022
"github.com/hyperledger/fabric-x-orderer/common/tools/armageddon"
23+
"github.com/hyperledger/fabric-x-orderer/common/types"
2124
"github.com/hyperledger/fabric-x-orderer/node/comm"
25+
"github.com/hyperledger/fabric-x-orderer/node/crypto"
2226
"github.com/hyperledger/fabric-x-orderer/testutil/tx"
2327
)
2428

@@ -27,6 +31,9 @@ type StreamInfo struct {
2731
totalTxSent uint32
2832
endpoint string
2933
streamLock sync.Mutex
34+
partyID types.PartyID
35+
signer *crypto.ECDSASigner
36+
certBytes []byte
3037
}
3138

3239
var logger = flogging.MustGetLogger("BroadcastClient")
@@ -42,7 +49,7 @@ func NewBroadcastTxClient(userConfigFile *armageddon.UserConfig, timeOut time.Du
4249
return &BroadcastTxClient{
4350
userConfig: userConfigFile,
4451
timeOut: timeOut,
45-
streamRoutersMap: make(map[string]*StreamInfo, len(userConfigFile.RouterEndpoints)),
52+
streamRoutersMap: make(map[string]*StreamInfo, len(userConfigFile.RouterUserConfigs)),
4653
}
4754
}
4855

@@ -78,7 +85,7 @@ func (c *BroadcastTxClient) SendTx(txContent []byte) error {
7885
streamInfo.streamLock.Lock()
7986
defer streamInfo.streamLock.Unlock()
8087

81-
env := tx.CreateStructuredEnvelope(txContent)
88+
env := tx.CreateSignedStructuredEnvelope(txContent, streamInfo.signer, streamInfo.certBytes, fmt.Sprintf("org%d", streamInfo.partyID))
8289
err := streamInfo.stream.Send(env)
8390
if err != nil {
8491
updateStateLock.Lock()
@@ -115,9 +122,9 @@ func (c *BroadcastTxClient) SendTx(txContent []byte) error {
115122

116123
waitForReceiveDone.Wait()
117124
// check if we had any failures
118-
possibleNumOfFailures := len(c.userConfig.RouterEndpoints)
119-
if len(c.userConfig.RouterEndpoints) >= 3 {
120-
possibleNumOfFailures = len(c.userConfig.RouterEndpoints) / 3
125+
possibleNumOfFailures := len(c.userConfig.RouterUserConfigs)
126+
if len(c.userConfig.RouterUserConfigs) >= 3 {
127+
possibleNumOfFailures = len(c.userConfig.RouterUserConfigs) / 3
121128
}
122129
if failures > possibleNumOfFailures {
123130
er := fmt.Sprintf("\nfailed to send tx to %d out of %d send streams", failures, len(c.streamRoutersMap))
@@ -165,15 +172,20 @@ func (c *BroadcastTxClient) createSendStreams() error {
165172
}
166173

167174
// create gRPC clients and streams to the routers
168-
for i := 0; i < len(userConfig.RouterEndpoints); i++ {
169-
routerEndpoint := userConfig.RouterEndpoints[i]
175+
// routerEnpoints are sorted in the userConfig in the same order as party IDs
176+
for _, routerUserConfig := range userConfig.RouterUserConfigs {
177+
routerEndpoint := routerUserConfig.Endpoint
170178
streamInfo, ok := c.streamRoutersMap[routerEndpoint]
171179
if !ok {
172180
// create a gRPC connection to the router
173181
stream := createSendStream(userConfig, serverRootCAs, routerEndpoint)
174182
// if stream is created successfully, add it to the map
175183
if stream != nil {
176-
c.streamRoutersMap[routerEndpoint] = &StreamInfo{stream: stream, totalTxSent: 0, endpoint: routerEndpoint}
184+
signer, certBytes, err := buildCryptoMaterials(routerUserConfig.UserMSPDir)
185+
if err != nil {
186+
return fmt.Errorf("failed to build crypto materials: %v", err)
187+
}
188+
c.streamRoutersMap[routerEndpoint] = &StreamInfo{stream: stream, totalTxSent: 0, endpoint: routerEndpoint, partyID: routerUserConfig.PartyID, signer: signer, certBytes: certBytes}
177189
}
178190
} else {
179191
if streamInfo.stream == nil {
@@ -192,6 +204,24 @@ func (c *BroadcastTxClient) createSendStreams() error {
192204
return nil
193205
}
194206

207+
func buildCryptoMaterials(configDir string) (*crypto.ECDSASigner, []byte, error) {
208+
keyBytes, err := os.ReadFile(filepath.Join(configDir, "keystore/priv_sk"))
209+
if err != nil {
210+
return nil, nil, fmt.Errorf("failed to read private key file: %v", err)
211+
}
212+
signer, err := tx.CreateECDSASigner(keyBytes)
213+
if err != nil {
214+
return nil, nil, fmt.Errorf("failed to create signer from private key: %v", err)
215+
}
216+
217+
certBytes, err := os.ReadFile(filepath.Join(configDir, "signcerts/sign-cert.pem"))
218+
if err != nil {
219+
return nil, nil, fmt.Errorf("failed to read sign certificate file: %v", err)
220+
}
221+
222+
return signer, certBytes, nil
223+
}
224+
195225
func (c *BroadcastTxClient) Stop() {
196226
c.streamsMapLock.Lock()
197227
defer c.streamsMapLock.Unlock()

testutil/tx/tx_utils.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"time"
1818

1919
"github.com/hyperledger/fabric-protos-go-apiv2/common"
20+
"github.com/hyperledger/fabric-protos-go-apiv2/msp"
2021
"github.com/hyperledger/fabric-x-common/protoutil"
2122
"github.com/hyperledger/fabric-x-orderer/node/crypto"
2223
protos "github.com/hyperledger/fabric-x-orderer/node/protos/comm"
@@ -209,3 +210,34 @@ func PrepareEnvWithTimestamp(txNumber int, envSize int, sessionNumber []byte) *c
209210
data := PrepareTxWithTimestamp(txNumber, dataSize, sessionNumber)
210211
return CreateStructuredEnvelope(data)
211212
}
213+
214+
func CreateSignedStructuredEnvelope(data []byte, signer *crypto.ECDSASigner, certBytes []byte, org string) *common.Envelope {
215+
payload := createSignedStructuredPayload(data, certBytes, org)
216+
payloadBytes := deterministicMarshall(payload)
217+
signature, err := signer.Sign(payloadBytes)
218+
if err != nil {
219+
return nil
220+
}
221+
return &common.Envelope{
222+
Payload: payloadBytes,
223+
Signature: signature,
224+
}
225+
}
226+
227+
func createSignedStructuredPayload(data []byte, certBytes []byte, org string) *common.Payload {
228+
payloadChannelHeader := createChannelHeader(common.HeaderType_MESSAGE, 0, "channelID", 0)
229+
230+
sId := msp.SerializedIdentity{
231+
Mspid: org,
232+
IdBytes: certBytes,
233+
}
234+
235+
payloadSignatureHeader := &common.SignatureHeader{
236+
Creator: deterministicMarshall(&sId),
237+
Nonce: []byte("nonce"),
238+
}
239+
return &common.Payload{
240+
Header: createPayloadHeader(payloadChannelHeader, payloadSignatureHeader),
241+
Data: data,
242+
}
243+
}

0 commit comments

Comments
 (0)