Environment
- fabric-x-committer v1.0.4 (08a469d)
- fabric-x-common v0.2.8 (via the committer's go.mod)
- fabric-x-orderer v1.0.3, arma consensus, 4 orderer orgs
- TLS PKI: one network TLS root CA → one intermediate CA per org → node leaf certificates
Symptom
With node TLS leaves signed by per-org intermediate CAs (instead of a root that signs
leaves directly), the sidecar cannot establish any orderer connection:
transport: authentication handshake failed: tls: failed to verify certificate:
x509: certificate signed by unknown authority
No blocks are delivered, and every client waiting on a transaction status times out
(failed to wait for transaction status event: context deadline exceeded).
The config block is not the problem: the org MSPs in it carry both tls_root_certs
and tls_intermediate_certs (standard MSP directories, assembled by configtxgen).
Root cause
Two call sites build client TLS trust pools from the config block using
GetTLSRootCerts() only, silently dropping the intermediates the config carries:
-
fabric-x-common, common/channelconfig/load.go (LoadConfigBlockMaterial), consumed
by the committer's utils/ordererdial.NewDialInfo for orderer connections:
CACerts: org.MSP().GetTLSRootCerts(),
-
fabric-x-committer, utils/serialization/transaction.go
(ExtractAppTLSCAsFromEnvelope), for application-org client verification:
certs = append(certs, org.MSP().GetTLSRootCerts()...)
Servers cannot compensate by presenting a chained certificate file: the envelope TLS
certificate hash is computed over the whole server certificate file, so server.crt
must stay leaf-only. With a leaf-only presentation and a roots-only pool, chain
building has no path to the trust anchor.
Single-tier deployments never see this because there are no intermediates to drop —
which is presumably why it survived: every leaf is signed by the root itself.
Fix
Append the intermediates at both sites:
// load.go
CACerts: append(org.MSP().GetTLSRootCerts(), org.MSP().GetTLSIntermediateCerts()...),
// transaction.go
certs = append(certs, org.MSP().GetTLSRootCerts()...)
certs = append(certs, org.MSP().GetTLSIntermediateCerts()...)
We run a committer build with exactly these two one-line patches applied and the
handshake failures disappear; the same topology commits transactions end to end.
(Verification detail: openssl verify with the pool contents confirms the chain is
valid the moment the intermediate is present; the certificates were never the issue.)
Happy to submit PRs for both repositories if the direction looks right to you.
Reproduction sketch
- Create a TLS root CA and one intermediate CA per orderer org; sign every node TLS
leaf with its org's intermediate.
- Lay out standard MSP directories (
tlscacerts/ = root, tlsintermediatecerts/ =
intermediate) and produce the genesis block with configtxgen.
- Start orderers and the committer sidecar; the sidecar's orderer dials fail with
certificate signed by unknown authority even though the config block contains the
full chain material.
Environment
Symptom
With node TLS leaves signed by per-org intermediate CAs (instead of a root that signs
leaves directly), the sidecar cannot establish any orderer connection:
No blocks are delivered, and every client waiting on a transaction status times out
(
failed to wait for transaction status event: context deadline exceeded).The config block is not the problem: the org MSPs in it carry both
tls_root_certsand
tls_intermediate_certs(standard MSP directories, assembled by configtxgen).Root cause
Two call sites build client TLS trust pools from the config block using
GetTLSRootCerts()only, silently dropping the intermediates the config carries:fabric-x-common,
common/channelconfig/load.go(LoadConfigBlockMaterial), consumedby the committer's
utils/ordererdial.NewDialInfofor orderer connections:fabric-x-committer,
utils/serialization/transaction.go(
ExtractAppTLSCAsFromEnvelope), for application-org client verification:Servers cannot compensate by presenting a chained certificate file: the envelope TLS
certificate hash is computed over the whole server certificate file, so
server.crtmust stay leaf-only. With a leaf-only presentation and a roots-only pool, chain
building has no path to the trust anchor.
Single-tier deployments never see this because there are no intermediates to drop —
which is presumably why it survived: every leaf is signed by the root itself.
Fix
Append the intermediates at both sites:
We run a committer build with exactly these two one-line patches applied and the
handshake failures disappear; the same topology commits transactions end to end.
(Verification detail:
openssl verifywith the pool contents confirms the chain isvalid the moment the intermediate is present; the certificates were never the issue.)
Happy to submit PRs for both repositories if the direction looks right to you.
Reproduction sketch
leaf with its org's intermediate.
tlscacerts/= root,tlsintermediatecerts/=intermediate) and produce the genesis block with configtxgen.
certificate signed by unknown authorityeven though the config block contains thefull chain material.