@@ -7,18 +7,28 @@ SPDX-License-Identifier: Apache-2.0
77package protoutil_test
88
99import (
10+ "crypto/ecdsa"
11+ "crypto/elliptic"
12+ "crypto/rand"
13+ "crypto/sha256"
14+ "crypto/tls"
15+ "crypto/x509"
1016 "encoding/hex"
17+ "encoding/pem"
1118 "errors"
1219 "strconv"
1320 "strings"
1421 "testing"
1522
1623 cb "github.com/hyperledger/fabric-protos-go-apiv2/common"
1724 pb "github.com/hyperledger/fabric-protos-go-apiv2/peer"
25+ "google.golang.org/grpc/credentials"
26+ "google.golang.org/grpc/peer"
1827 "google.golang.org/protobuf/proto"
1928
2029 "github.com/stretchr/testify/require"
2130
31+ "github.com/hyperledger/fabric-x-common/common/util"
2232 "github.com/hyperledger/fabric-x-common/protoutil"
2333 "github.com/hyperledger/fabric-x-common/protoutil/identity/mocks"
2434)
@@ -120,6 +130,85 @@ func TestGetPayloads(t *testing.T) {
120130 t .Logf ("error6 [%s]" , err )
121131}
122132
133+ func TestHashTLSCertificate (t * testing.T ) {
134+ t .Parallel ()
135+
136+ t .Run ("PEM encoded certificate" , func (t * testing.T ) {
137+ t .Parallel ()
138+ _ , certPEM := generateTestCertPEM (t , "CERTIFICATE" )
139+
140+ hash , err := protoutil .HashTLSCertificate (certPEM )
141+ require .NoError (t , err )
142+ require .NotNil (t , hash )
143+ require .Len (t , hash , 32 ) // SHA256 produces 32 bytes
144+
145+ // The hash should be deterministic.
146+ hash2 , err := protoutil .HashTLSCertificate (certPEM )
147+ require .NoError (t , err )
148+ require .Equal (t , hash , hash2 )
149+ })
150+
151+ t .Run ("Hash matches server-side extraction" , func (t * testing.T ) {
152+ t .Parallel ()
153+ certDER , certPEM := generateTestCertPEM (t , "CERTIFICATE" )
154+
155+ // Simulate server-side: create a gRPC context with TLS peer info
156+ cert , err := x509 .ParseCertificate (certDER )
157+ require .NoError (t , err )
158+ ctx := peer .NewContext (t .Context (), & peer.Peer {
159+ AuthInfo : credentials.TLSInfo {
160+ State : tls.ConnectionState {PeerCertificates : []* x509.Certificate {cert }},
161+ },
162+ })
163+ // Use ExtractRawCertificateFromContext (server-side).
164+ rawCert := util .ExtractRawCertificateFromContext (ctx )
165+ require .NotNil (t , rawCert )
166+
167+ // Hash the raw certificate (server-side).
168+ serverHash := sha256 .Sum256 (rawCert )
169+
170+ // Hash using HashTLSCertificate (client-side).
171+ clientHash , err := protoutil .HashTLSCertificate (certPEM )
172+ require .NoError (t , err )
173+
174+ // Verify both hashes match.
175+ require .Equal (t , serverHash [:], clientHash , "Client-side hash should match server-side hash" )
176+ })
177+
178+ _ , wrongCertPEMBlockType := generateTestCertPEM (t , "EC PRIVATE KEY" )
179+ for _ , tc := range []struct {
180+ name string
181+ cert []byte
182+ }{
183+ {name : "Empty certificate" , cert : nil },
184+ {name : "Invalid certificate" , cert : []byte ("not a valid PEM certificate" )},
185+ {name : "Wrong PEM block type" , cert : wrongCertPEMBlockType },
186+ } {
187+ t .Run (tc .name , func (t * testing.T ) {
188+ t .Parallel ()
189+ _ , err := protoutil .HashTLSCertificate (tc .cert )
190+ require .ErrorContains (t , err , "failed to find any PEM data in certificate input" )
191+ })
192+ }
193+ }
194+
195+ func generateTestCertPEM (t * testing.T , blockType string ) (certDER , certPEM []byte ) {
196+ t .Helper ()
197+
198+ priv , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
199+ require .NoError (t , err )
200+
201+ template := & x509.Certificate {}
202+ certDER , err = x509 .CreateCertificate (rand .Reader , template , template , & priv .PublicKey , priv )
203+ require .NoError (t , err )
204+ require .NotNil (t , certDER )
205+
206+ certPEM = pem .EncodeToMemory (& pem.Block {Type : blockType , Bytes : certDER })
207+ require .NotNil (t , certPEM )
208+
209+ return certDER , certPEM
210+ }
211+
123212func TestDeduplicateEndorsements (t * testing.T ) {
124213 signID := & mocks.SignerSerializer {}
125214 signID .SerializeReturns ([]byte ("signer" ), nil )
0 commit comments