diff --git a/utils/serialization/envelope_wrapper.go b/utils/serialization/envelope_wrapper.go index 3c674e11..b65f7cef 100644 --- a/utils/serialization/envelope_wrapper.go +++ b/utils/serialization/envelope_wrapper.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package serialization import ( + "errors" "fmt" "github.com/hyperledger/fabric-protos-go-apiv2/common" @@ -19,14 +20,24 @@ import ( // NoOpSigner supports unsigned envelopes. type NoOpSigner struct{} -// WrapEnvelope serialize envelope. -func WrapEnvelope(data []byte, header *common.Header) []byte { +// WrapEnvelopePayload serializes envelope payload. +func WrapEnvelopePayload(data []byte, header *common.Header) []byte { return protoutil.MarshalOrPanic(&common.Payload{ Header: header, Data: data, }) } +// WrapEnvelope wraps a payload with its header and returns an envelope. +func WrapEnvelope(data []byte, header *common.Header) []byte { + payloadBytes := WrapEnvelopePayload(data, header) + + envelope := &common.Envelope{ + Payload: payloadBytes, + } + return protoutil.MarshalOrPanic(envelope) +} + // UnwrapEnvelope deserialize an envelope. func UnwrapEnvelope(message []byte) ([]byte, *common.ChannelHeader, error) { envelope, err := protoutil.GetEnvelopeFromBlock(message) @@ -48,6 +59,11 @@ func ParseEnvelope(envelope *common.Envelope) (*common.Payload, *common.ChannelH if err != nil { return nil, nil, err } + + if payload.Header == nil { + return nil, nil, errors.New("payload header is nil") + } + channelHdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader) if err != nil { return nil, nil, err @@ -81,7 +97,7 @@ func CreateEnvelope( channelHeader.TxId = protoutil.ComputeTxID(signatureHeader.Nonce, signatureHeader.Creator) } payloadHeader := protoutil.MakePayloadHeader(channelHeader, signatureHeader) - payload := WrapEnvelope(data, payloadHeader) + payload := WrapEnvelopePayload(data, payloadHeader) signature, err := signer.Sign(payload) if err != nil { diff --git a/utils/serialization/envelope_wrapper_test.go b/utils/serialization/envelope_wrapper_test.go new file mode 100644 index 00000000..72c646d2 --- /dev/null +++ b/utils/serialization/envelope_wrapper_test.go @@ -0,0 +1,96 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package serialization_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric/protoutil" + + "github.com/hyperledger/fabric-x-committer/utils/serialization" + "github.com/hyperledger/fabric-x-committer/utils/test" +) + +// TestUnwrapEnvelopeBadInput tests UnwrapEnvelope function with invalid inputs. +func TestUnwrapEnvelopeBadInput(t *testing.T) { + t.Run("Not an envelope", func(t *testing.T) { + t.Parallel() + _, _, err := serialization.UnwrapEnvelope([]byte("invalid input")) + require.Error(t, err) + }) + + t.Run("OK Header with an invalid payload", func(t *testing.T) { + t.Parallel() + envelope := &common.Envelope{ + Payload: []byte("not-a-payload"), + } + + envelopeBytes := protoutil.MarshalOrPanic(envelope) + + _, _, err := serialization.UnwrapEnvelope(envelopeBytes) + require.Error(t, err) + }) + + t.Run("OK Payload with a nil Header", func(t *testing.T) { + t.Parallel() + payload := &common.Payload{ + Header: nil, + Data: []byte("some data"), + } + payloadBytes := protoutil.MarshalOrPanic(payload) + + envelope := &common.Envelope{ + Payload: payloadBytes, + } + envelopeBytes := protoutil.MarshalOrPanic(envelope) + + _, _, err := serialization.UnwrapEnvelope(envelopeBytes) + require.Error(t, err) + }) + + t.Run("OK payload but invalid ChannelHeader", func(t *testing.T) { + t.Parallel() + header := &common.Header{ + ChannelHeader: []byte("not-a-channel-header"), + } + payload := &common.Payload{ + Header: header, + Data: []byte("some data"), + } + payloadBytes := protoutil.MarshalOrPanic(payload) + + envelope := &common.Envelope{ + Payload: payloadBytes, + } + envelopeBytes := protoutil.MarshalOrPanic(envelope) + + _, _, err := serialization.UnwrapEnvelope(envelopeBytes) + require.Error(t, err) + }) +} + +// TestUnwrapEnvelopeGoodInput Tests properly wrapped envelope is unwrapped correctly. +func TestUnwrapEnvelopeGoodInput(t *testing.T) { + t.Parallel() + // -1- Check unwrap envelope has no error + originalPayload := []byte("test payload") + originalChannelHeader := &common.ChannelHeader{ + ChannelId: "test-channel", + } + originalHeader := &common.Header{ + ChannelHeader: protoutil.MarshalOrPanic(originalChannelHeader), + } + wrappedEnvelope := serialization.WrapEnvelope(originalPayload, originalHeader) + payload, channelHeader, err := serialization.UnwrapEnvelope(wrappedEnvelope) + + // -2- Check we get the correct Payload & Header + require.NoError(t, err) + require.Equal(t, originalPayload, payload) + test.RequireProtoEqual(t, originalChannelHeader, channelHeader) +} diff --git a/utils/signature/sigtest/crypto.go b/utils/signature/sigtest/crypto.go index 99d2e05d..0aa7927d 100644 --- a/utils/signature/sigtest/crypto.go +++ b/utils/signature/sigtest/crypto.go @@ -32,10 +32,15 @@ func SerializeVerificationKey(key *ecdsa.PublicKey) ([]byte, error) { // SerializeSigningKey encodes a ECDSA private key into a PEM file. func SerializeSigningKey(key *ecdsa.PrivateKey) ([]byte, error) { + if key == nil { + return nil, errors.New("key is nil") + } + x509encodedPri, err := x509.MarshalECPrivateKey(key) if err != nil { return nil, errors.Wrap(err, "cannot serialize private key") } + return pem.EncodeToMemory(&pem.Block{ Type: "EC PRIVATE KEY", Bytes: x509encodedPri, diff --git a/utils/signature/sigtest/crypto_test.go b/utils/signature/sigtest/crypto_test.go new file mode 100644 index 00000000..2b95750a --- /dev/null +++ b/utils/signature/sigtest/crypto_test.go @@ -0,0 +1,110 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package sigtest + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSerializeVerificationKey(t *testing.T) { + t.Parallel() + tests := []struct { + name string + curve elliptic.Curve + wantErr bool + }{ + { + name: "P256", + curve: elliptic.P256(), + }, + { + name: "P384", + curve: elliptic.P384(), + }, + { + name: "P224", + curve: elliptic.P224(), + }, + { + name: "P521", + curve: elliptic.P521(), + }, + + // { + // // TODO: find an invalid example? + // name: "Invalid input", + // curve: elliptic.(), + // wantErr: true, + // }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + privKey, err := ecdsa.GenerateKey(tt.curve, rand.Reader) + require.NoError(t, err) + + _, err = SerializeVerificationKey(&privKey.PublicKey) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestSerializeSigningKey(t *testing.T) { + // Panic can happen from unwanted side effects when nil and empty Keys are passed + defer func() { + if r := recover(); r != nil { + t.Errorf("SerializeSigningKey() panics: %v", r) + } + }() + + t.Run("Key Empty", func(t *testing.T) { + t.Parallel() + emptyKey := &ecdsa.PrivateKey{} + _, err := SerializeSigningKey(emptyKey) + require.Error(t, err) + }) + t.Run("Key is nil", func(t *testing.T) { + t.Parallel() + _, err := SerializeSigningKey(nil) + require.Error(t, err) + }) + + t.Run("Key OK", func(t *testing.T) { + t.Parallel() + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + key, err := SerializeSigningKey(privateKey) + require.NotNil(t, key) + require.NoError(t, err) + }) +} + +func TestParseSigningKey(t *testing.T) { + t.Run("Key is nil", func(t *testing.T) { + t.Parallel() + _, err := ParseSigningKey(nil) + require.Error(t, err) + }) + t.Run("Key OK", func(t *testing.T) { + t.Parallel() + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + key, err := SerializeSigningKey(privateKey) + require.NoError(t, err) + _, err = ParseSigningKey(key) + require.NoError(t, err) + }) +}