Skip to content

Commit d8f6bbb

Browse files
authored
Allow network to keep working after renewing certs with the same key (#5268)
* Allow network to keep working after renewing certs with the same key pair in BFT Signed-off-by: David VIEJO <[email protected]> * Refactor certificate verification in dialers - Updated the `VerifyCertificate` function in `PredicateDialer` to use the provided `verifyFunc` instead of a hardcoded function. - Removed the logging and verification function in `StandardDialer`, setting it to `nil` for simplicity. This change enhances flexibility in certificate verification while cleaning up unnecessary code. Signed-off-by: David VIEJO <[email protected]> * Remove logging and some comments Signed-off-by: David VIEJO <[email protected]> * Improve logging Signed-off-by: David VIEJO <[email protected]> * Update Signed-off-by: David VIEJO <[email protected]> * Add BFT test to verify renewal of certificates is working Signed-off-by: David VIEJO <[email protected]> * Refactor certificate handling in SmartBFT and Cluster services - Renamed `renewOrdererCertificates` to `renewOrdererTLSCertificates` for clarity. - Introduced `renewOrdererEnrollmentCertificates` to handle enrollment certificate renewal. - Moved public key extraction logic to `util.go` and updated references in `consenter.go` and `clusterservice.go` to use the new method. - Improved logging for certificate processing errors. This refactor enhances code clarity and maintains consistency in certificate management across the codebase. Signed-off-by: David VIEJO <[email protected]> * Enhance certificate comparison functionality in Cluster service - Refactored the `compareCertPublicKeys` function to `CompareCertPublicKeys` for improved clarity and consistency across the codebase. - Updated references in `clusterservice.go` and `util.go` to utilize the new function. - Added comprehensive unit tests for `CompareCertPublicKeys`, covering scenarios with certificates having the same public keys but different bytes, as well as handling errors gracefully for malformed certificates. These changes improve the robustness of certificate handling and verification in the Cluster service. Signed-off-by: David VIEJO <[email protected]> * Update logging configuration in SmartBFT end-to-end tests - Modified the logging specification for orderer runners in `smartbft_test.go` to focus on SmartBFT and gRPC debug logs, enhancing the clarity of test outputs. Signed-off-by: David VIEJO <[email protected]> * Updates based on the comments Signed-off-by: David VIEJO <[email protected]> * Add chaincode deployment and invocation tests in SmartBFT integration tests - Implemented chaincode deployment and multiple invocation queries in the end-to-end SmartBFT configuration test. - Removed unused certificate renewal functions to streamline the code. These enhancements improve the testing coverage for SmartBFT and ensure proper chaincode functionality. Signed-off-by: David VIEJO <[email protected]> * Fixed formatting Signed-off-by: dviejokfs <[email protected]> * Remove unused logging statement in SmartBFT certificate renewal process to streamline code and improve clarity. Signed-off-by: David VIEJO <[email protected]> --------- Signed-off-by: David VIEJO <[email protected]> Signed-off-by: dviejokfs <[email protected]>
1 parent d8adf9e commit d8f6bbb

File tree

6 files changed

+825
-8
lines changed

6 files changed

+825
-8
lines changed

integration/smartbft/smartbft_test.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"bytes"
1212
"compress/gzip"
1313
"context"
14+
"crypto/ecdsa"
15+
"crypto/rand"
1416
"crypto/x509"
1517
"encoding/pem"
1618
"fmt"
@@ -165,7 +167,118 @@ var _ = Describe("EndToEnd Smart BFT configuration test", func() {
165167
By("invoking the chaincode, again")
166168
invokeQuery(network, peer, network.Orderers[2], channel, 80)
167169
})
170+
It("disregards certificate renewal if only the validity period changed", func() {
171+
networkConfig := nwo.MultiNodeSmartBFT()
172+
networkConfig.Channels = nil
173+
174+
network = nwo.New(networkConfig, testDir, client, StartPort(), components)
175+
network.GenerateConfigTree()
176+
network.Bootstrap()
177+
178+
var ordererRunners []*ginkgomon.Runner
179+
for _, orderer := range network.Orderers {
180+
runner := network.OrdererRunner(orderer)
181+
runner.Command.Env = append(runner.Command.Env, "FABRIC_LOGGING_SPEC=orderer.consensus.smartbft=debug:grpc=debug")
182+
ordererRunners = append(ordererRunners, runner)
183+
proc := ifrit.Invoke(runner)
184+
ordererProcesses = append(ordererProcesses, proc)
185+
Eventually(proc.Ready(), network.EventuallyTimeout).Should(BeClosed())
186+
}
187+
188+
peerRunner := network.PeerGroupRunner()
189+
peerProcesses = ifrit.Invoke(peerRunner)
190+
191+
Eventually(peerProcesses.Ready(), network.EventuallyTimeout).Should(BeClosed())
192+
peer := network.Peer("Org1", "peer0")
193+
194+
sess, err := network.ConfigTxGen(commands.OutputBlock{
195+
ChannelID: "testchannel1",
196+
Profile: network.Profiles[0].Name,
197+
ConfigPath: network.RootDir,
198+
OutputBlock: network.OutputBlockPath("testchannel1"),
199+
})
200+
Expect(err).NotTo(HaveOccurred())
201+
Eventually(sess, network.EventuallyTimeout).Should(gexec.Exit(0))
202+
203+
genesisBlockBytes, err := os.ReadFile(network.OutputBlockPath("testchannel1"))
204+
Expect(err).NotTo(HaveOccurred())
205+
206+
genesisBlock := &common.Block{}
207+
err = proto.Unmarshal(genesisBlockBytes, genesisBlock)
208+
Expect(err).NotTo(HaveOccurred())
209+
210+
expectedChannelInfoPT := nwo.ChannelInfo{
211+
Name: "testchannel1",
212+
URL: "/participation/v1/channels/testchannel1",
213+
Status: "active",
214+
ConsensusRelation: "consenter",
215+
Height: 1,
216+
}
217+
218+
for _, o := range network.Orderers {
219+
By("joining " + o.Name + " to channel as a consenter")
220+
nwo.Join(network, o, "testchannel1", genesisBlock, expectedChannelInfoPT)
221+
channelInfo := nwo.ListOne(network, o, "testchannel1")
222+
Expect(channelInfo).To(Equal(expectedChannelInfoPT))
223+
}
224+
225+
By("Waiting for followers to see the leader")
226+
Eventually(ordererRunners[1].Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say("Message from 1"))
227+
Eventually(ordererRunners[2].Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say("Message from 1"))
228+
Eventually(ordererRunners[3].Err(), network.EventuallyTimeout, time.Second).Should(gbytes.Say("Message from 1"))
229+
230+
channel := "testchannel1"
231+
By(fmt.Sprintf("Peers with Channel %s are %+v\n", channel, network.PeersWithChannel(channel)))
232+
orderer := network.Orderers[0]
233+
network.JoinChannel(channel, orderer, network.PeersWithChannel(channel)...)
234+
235+
By("Killing all orderers")
236+
for i := range network.Orderers {
237+
ordererProcesses[i].Signal(syscall.SIGTERM)
238+
Eventually(ordererProcesses[i].Wait(), network.EventuallyTimeout).Should(Receive())
239+
}
240+
241+
By("Renewing the TLS certificates of the orderers")
242+
renewOrdererTLSCertificates(network, network.Orderers...)
243+
244+
By("Renewing the enrollment certificates of the orderers")
245+
renewOrdererEnrollmentCertificates(network, time.Now().Add(time.Hour), network.Orderers...)
246+
247+
By("Starting the orderers again")
248+
for i := range network.Orderers {
249+
ordererRunner := network.OrdererRunner(network.Orderers[i])
250+
ordererRunners[i] = ordererRunner
251+
ordererProcesses[i] = ifrit.Invoke(ordererRunner)
252+
Eventually(ordererProcesses[i].Ready(), network.EventuallyTimeout).Should(BeClosed())
253+
}
254+
updateBatchSize(network, peer, orderer, channel, func(batchSize *ordererProtos.BatchSize) {
255+
batchSize.AbsoluteMaxBytes = 1000000
256+
batchSize.MaxMessageCount = 300
257+
})
258+
assertBlockReception(map[string]int{"testchannel1": 1}, network.Orderers, peer, network)
259+
260+
updateBatchSize(network, peer, orderer, channel, func(batchSize *ordererProtos.BatchSize) {
261+
batchSize.AbsoluteMaxBytes = 1000000
262+
batchSize.MaxMessageCount = 400
263+
})
264+
265+
assertBlockReception(map[string]int{"testchannel1": 2}, network.Orderers, peer, network)
266+
267+
By("Try deploying chaincode")
268+
peers := network.PeersWithChannel(channel)
269+
Expect(len(peers)).ToNot(Equal(0))
168270

271+
// deploy the chaincode
272+
deployChaincode(network, channel, testDir)
273+
274+
assertBlockReception(map[string]int{"testchannel1": 6}, network.Orderers, peer, network)
275+
276+
// test the chaincodes
277+
invokeQuery(network, peer, orderer, channel, 90)
278+
invokeQuery(network, peer, orderer, channel, 80)
279+
invokeQuery(network, peer, orderer, channel, 70)
280+
invokeQuery(network, peer, orderer, channel, 60)
281+
})
169282
It("smartbft node addition and removal", func() {
170283
networkConfig := nwo.MultiNodeSmartBFT()
171284
networkConfig.Channels = nil
@@ -2758,3 +2871,129 @@ func createPrePrepareRequest(
27582871

27592872
return req, block
27602873
}
2874+
2875+
func renewOrdererTLSCertificates(network *nwo.Network, orderers ...*nwo.Orderer) {
2876+
if len(orderers) == 0 {
2877+
return
2878+
}
2879+
ordererDomain := network.Organization(orderers[0].Organization).Domain
2880+
ordererTLSCAKeyPath := filepath.Join(network.RootDir, "crypto", "ordererOrganizations",
2881+
ordererDomain, "tlsca", "priv_sk")
2882+
2883+
ordererTLSCAKey, err := os.ReadFile(ordererTLSCAKeyPath)
2884+
Expect(err).NotTo(HaveOccurred())
2885+
2886+
ordererTLSCACertPath := filepath.Join(network.RootDir, "crypto", "ordererOrganizations",
2887+
ordererDomain, "tlsca", fmt.Sprintf("tlsca.%s-cert.pem", ordererDomain))
2888+
ordererTLSCACert, err := os.ReadFile(ordererTLSCACertPath)
2889+
Expect(err).NotTo(HaveOccurred())
2890+
2891+
serverTLSCerts := map[string][]byte{}
2892+
for _, orderer := range orderers {
2893+
tlsCertPath := filepath.Join(network.OrdererLocalTLSDir(orderer), "server.crt")
2894+
serverTLSCerts[tlsCertPath], err = os.ReadFile(tlsCertPath)
2895+
Expect(err).NotTo(HaveOccurred())
2896+
}
2897+
2898+
for filePath, certPEM := range serverTLSCerts {
2899+
renewedCert := renewCertificate(certPEM, ordererTLSCACert, ordererTLSCAKey, time.Now().Add(time.Hour))
2900+
err = os.WriteFile(filePath, renewedCert, 0o600)
2901+
Expect(err).NotTo(HaveOccurred())
2902+
}
2903+
}
2904+
2905+
// renewCertificate generates a new certificate with the same public key and subject as the original,
2906+
// but with a new NotAfter (expiration time). Only the expiration is changed.
2907+
func renewCertificate(certPEM, caCertPEM, caKeyPEM []byte, notAfter time.Time) (renewedCertPEM []byte) {
2908+
// Parse CA private key
2909+
keyAsDER, _ := pem.Decode(caKeyPEM)
2910+
caKeyWithoutType, err := x509.ParsePKCS8PrivateKey(keyAsDER.Bytes)
2911+
Expect(err).NotTo(HaveOccurred())
2912+
caKey := caKeyWithoutType.(*ecdsa.PrivateKey)
2913+
2914+
// Parse CA certificate
2915+
caCertAsDER, _ := pem.Decode(caCertPEM)
2916+
caCert, err := x509.ParseCertificate(caCertAsDER.Bytes)
2917+
Expect(err).NotTo(HaveOccurred())
2918+
2919+
// Parse the original certificate
2920+
certAsDER, _ := pem.Decode(certPEM)
2921+
cert, err := x509.ParseCertificate(certAsDER.Bytes)
2922+
Expect(err).NotTo(HaveOccurred())
2923+
2924+
// Create a new certificate template with the same fields as the original,
2925+
// but with a new NotAfter (expiration time)
2926+
newCert := &x509.Certificate{
2927+
SerialNumber: cert.SerialNumber,
2928+
Subject: cert.Subject,
2929+
NotBefore: cert.NotBefore,
2930+
NotAfter: notAfter,
2931+
KeyUsage: cert.KeyUsage,
2932+
ExtKeyUsage: cert.ExtKeyUsage,
2933+
UnknownExtKeyUsage: cert.UnknownExtKeyUsage,
2934+
BasicConstraintsValid: cert.BasicConstraintsValid,
2935+
IsCA: cert.IsCA,
2936+
DNSNames: cert.DNSNames,
2937+
EmailAddresses: cert.EmailAddresses,
2938+
IPAddresses: cert.IPAddresses,
2939+
URIs: cert.URIs,
2940+
SubjectKeyId: cert.SubjectKeyId,
2941+
AuthorityKeyId: cert.AuthorityKeyId,
2942+
SignatureAlgorithm: cert.SignatureAlgorithm,
2943+
PublicKeyAlgorithm: cert.PublicKeyAlgorithm,
2944+
PublicKey: cert.PublicKey,
2945+
PolicyIdentifiers: cert.PolicyIdentifiers,
2946+
CRLDistributionPoints: cert.CRLDistributionPoints,
2947+
OCSPServer: cert.OCSPServer,
2948+
IssuingCertificateURL: cert.IssuingCertificateURL,
2949+
ExtraExtensions: cert.ExtraExtensions,
2950+
Extensions: cert.Extensions,
2951+
}
2952+
2953+
// The CA signs the new certificate
2954+
certBytes, err := x509.CreateCertificate(rand.Reader, newCert, caCert, cert.PublicKey, caKey)
2955+
Expect(err).NotTo(HaveOccurred())
2956+
2957+
renewedCertPEM = pem.EncodeToMemory(&pem.Block{Bytes: certBytes, Type: "CERTIFICATE"})
2958+
return
2959+
}
2960+
2961+
// renewOrdererEnrollmentCertificates renews the signcert for each orderer with a given expirationTime
2962+
// and re-writes it to the orderer's signcerts directory, matching the actual crypto structure.
2963+
func renewOrdererEnrollmentCertificates(network *nwo.Network, notAfter time.Time, orderers ...*nwo.Orderer) {
2964+
if len(orderers) == 0 {
2965+
return
2966+
}
2967+
2968+
for _, orderer := range orderers {
2969+
ordererDomain := network.Organization(orderer.Organization).Domain
2970+
// Use the orderer name as it appears in the file system, not the nwo.Orderer.ID()
2971+
// The directory is .../orderers/<ordererName>.<domain>/msp/signcerts/<ordererName>.<domain>-cert.pem
2972+
ordererName := orderer.Name
2973+
ordererFQDN := fmt.Sprintf("%s.%s", ordererName, ordererDomain)
2974+
2975+
// CA key and cert for the org
2976+
ordererCAKeyPath := filepath.Join(
2977+
network.RootDir, "crypto", "ordererOrganizations", ordererDomain, "ca", "priv_sk",
2978+
)
2979+
ordererCAKey, err := os.ReadFile(ordererCAKeyPath)
2980+
Expect(err).NotTo(HaveOccurred())
2981+
2982+
ordererCACertPath := filepath.Join(
2983+
network.RootDir, "crypto", "ordererOrganizations", ordererDomain, "ca", fmt.Sprintf("ca.%s-cert.pem", ordererDomain),
2984+
)
2985+
ordererCACert, err := os.ReadFile(ordererCACertPath)
2986+
Expect(err).NotTo(HaveOccurred())
2987+
2988+
// Path to the orderer's signcert
2989+
ordererSignCertPath := filepath.Join(
2990+
network.RootDir, "crypto", "ordererOrganizations", ordererDomain, "orderers", ordererFQDN, "msp", "signcerts", fmt.Sprintf("%s-cert.pem", ordererFQDN),
2991+
)
2992+
ordererSignCert, err := os.ReadFile(ordererSignCertPath)
2993+
Expect(err).NotTo(HaveOccurred())
2994+
2995+
renewedCert := renewCertificate(ordererSignCert, ordererCACert, ordererCAKey, notAfter)
2996+
err = os.WriteFile(ordererSignCertPath, renewedCert, 0o600)
2997+
Expect(err).NotTo(HaveOccurred())
2998+
}
2999+
}

orderer/common/cluster/clusterservice.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,12 @@ func (s *ClusterService) VerifyAuthRequest(stream orderer.ClusterNodeService_Ste
163163
return nil, errors.Errorf("node %d is not member of channel %s", authReq.ToId, authReq.Channel)
164164
}
165165

166-
if !bytes.Equal(toIdentity, s.NodeIdentity) {
166+
equal, err := CompareCertPublicKeys(toIdentity, s.NodeIdentity)
167+
if err != nil {
168+
return nil, errors.Wrap(err, "failed to compare cert public keys")
169+
}
170+
if !equal {
171+
s.Logger.Debugf("node id mismatch for node %d, toIdentity: %s, s.NodeIdentity: %s", authReq.FromId, string(toIdentity), string(s.NodeIdentity))
167172
return nil, errors.Errorf("node id mismatch")
168173
}
169174

@@ -263,6 +268,7 @@ func (c *ClusterService) ConfigureNodeCerts(channel string, newNodes []*common.C
263268
if err != nil {
264269
return err
265270
}
271+
266272
channelMembership.MemberMapping[uint64(nodeIdentity.Id)] = sanitizedID
267273
}
268274

0 commit comments

Comments
 (0)