From 847b79d1efcf6308e6a6aa1dddc53c6ef882f45d Mon Sep 17 00:00:00 2001 From: Jacob Barzee Date: Tue, 15 Jul 2025 01:27:26 -0700 Subject: [PATCH 1/2] x.509 and idemix, seperate load/store from key gen move credential loading into client split remaining file operations expose key generation functions to be used without side effects Signed-off-by: Jacob Barzee --- lib/client.go | 121 +++++++++++++++------ lib/client/credential/idemix/credential.go | 50 ++++++--- lib/client/credential/x509/credential.go | 52 ++++++--- lib/clientconfig.go | 19 +++- lib/tls/tls.go | 40 +++++-- lib/util.go | 29 +++-- 6 files changed, 221 insertions(+), 90 deletions(-) diff --git a/lib/client.go b/lib/client.go index a65add045..e72eb543d 100644 --- a/lib/client.go +++ b/lib/client.go @@ -206,6 +206,31 @@ func (c *Client) GetCAInfo(req *api.GetCAInfoRequest) (*GetCAInfoResponse, error return localSI, nil } +// GenerateKey generates a new key using the BCCSP provider. +// It performs only the cryptographic operation and does not write to disk. +func (c *Client) GenerateKey(cr *csr.CertificateRequest) (bccsp.Key, error) { + key, _, err := util.BCCSPKeyRequestGenerate(cr, c.csp) + if err != nil { + log.Debugf("failed generating BCCSP key: %s", err) + return nil, err + } + return key, nil +} + +// GenerateCSR generates a CSR using the provided key and certificate request. +// It performs only the cryptographic operation and does not write to disk. +func (c *Client) GenerateCSR(cr *csr.CertificateRequest, key bccsp.Key) ([]byte, error) { + cspSigner, err := cspsigner.New(c.csp, key) + if err != nil { + return nil, errors.WithMessage(err, "Failed initializing CryptoSigner") + } + csrPEM, err := csr.Generate(cspSigner, cr) + if err != nil { + return nil, errors.WithMessage(err, "Failed generating CSR") + } + return csrPEM, nil +} + // GenCSR generates a CSR (Certificate Signing Request) func (c *Client) GenCSR(req *api.CSRInfo, id string) ([]byte, bccsp.Key, error) { log.Debugf("GenCSR %+v", req) @@ -217,14 +242,13 @@ func (c *Client) GenCSR(req *api.CSRInfo, id string) ([]byte, bccsp.Key, error) cr := c.newCertificateRequest(req, id) - cspSigner, key, err := c.generateCSPSigner(cr, nil) + key, err := c.GenerateKey(cr) if err != nil { return nil, nil, err } - csrPEM, err := csr.Generate(cspSigner, cr) + csrPEM, err := c.GenerateCSR(cr, key) if err != nil { - log.Debugf("failed generating CSR: %s", err) return nil, nil, err } @@ -243,18 +267,12 @@ func (c *Client) GenCSRUsingKey(req *api.CSRInfo, id string, k bccsp.Key) ([]byt cr := c.newCertificateRequest(req, id) - cspSigner, key, err := c.generateCSPSigner(cr, k) - if err != nil { - return nil, nil, err - } - - csrPEM, err := csr.Generate(cspSigner, cr) + csrPEM, err := c.GenerateCSR(cr, k) if err != nil { - log.Debugf("failed generating CSR: %s", err) return nil, nil, err } - return csrPEM, key, nil + return csrPEM, k, nil } // generateCSPSigner generates a crypto.Signer for a given certificate request. @@ -617,6 +635,26 @@ func (c *Client) getIssuerPubKey(ipkBytes []byte) (*idemix.IssuerPublicKey, erro return c.issuerPublicKey, nil } +// loadX509Credential loads an X509 credential from the given cert and key files +func (c *Client) loadX509Credential(certFile, keyFile string) (credential.Credential, error) { + x509Cred := x509cred.NewCredential(certFile, keyFile, c) + err := x509Cred.Load() + if err != nil { + return nil, err + } + return x509Cred, nil +} + +// loadIdemixCredential loads an Idemix credential from the given file +func (c *Client) loadIdemixCredential(idemixCredFile string) (credential.Credential, error) { + idemixCred := idemixcred.NewCredential(idemixCredFile, c, c.curveID) + err := idemixCred.Load() + if err != nil { + return nil, err + } + return idemixCred, nil +} + // LoadMyIdentity loads the client's identity from disk func (c *Client) LoadMyIdentity() (*Identity, error) { log.Debugf("LoadMyIdentity ") @@ -637,8 +675,8 @@ func (c *Client) LoadIdentity(keyFile, certFile, idemixCredFile string) (*Identi var creds []credential.Credential var x509Found, idemixFound bool - x509Cred := x509cred.NewCredential(certFile, keyFile, c) - err = x509Cred.Load() + + x509Cred, err := c.loadX509Credential(certFile, keyFile) if err == nil { x509Found = true creds = append(creds, x509Cred) @@ -646,8 +684,7 @@ func (c *Client) LoadIdentity(keyFile, certFile, idemixCredFile string) (*Identi log.Debugf("No X509 credential found at %s, %s", keyFile, certFile) } - idemixCred := idemixcred.NewCredential(idemixCredFile, c, c.curveID) - err = idemixCred.Load() + idemixCred, err := c.loadIdemixCredential(idemixCredFile) if err == nil { idemixFound = true creds = append(creds, idemixCred) @@ -901,33 +938,54 @@ func (c *Client) CheckEnrollment() error { return errors.New("Enrollment information does not exist. Please execute enroll command first. Example: fabric-ca-client enroll -u http://user:userpw@serverAddr:serverPort") } -func (c *Client) checkX509Enrollment() error { +// x509EnrollmentFilesExist checks if the X509 key and cert files exist or if the key is stored by BCCSP +func (c *Client) x509EnrollmentFilesExist() bool { keyFileExists := util.FileExists(c.keyFile) certFileExists := util.FileExists(c.certFile) if keyFileExists && certFileExists { - return nil + return true } - // If key file does not exist, but certFile does, key file is probably - // stored by bccsp, so check to see if this is the case if certFileExists { _, _, _, err := util.GetSignerFromCertFile(c.certFile, c.csp) if err == nil { - // Yes, the key is stored by BCCSP - return nil + return true } } + return false +} + +// idemixEnrollmentFilesExist checks if the Idemix public key and credential files exist +func (c *Client) idemixEnrollmentFilesExist() bool { + idemixIssuerPubKeyExists := util.FileExists(c.ipkFile) + idemixCredExists := util.FileExists(c.idemixCredFile) + return idemixIssuerPubKeyExists && idemixCredExists +} + +// loadIdemixSignerConfig loads the Idemix signer config from file +func (c *Client) loadIdemixSignerConfig() (*idemixcred.SignerConfig, error) { + credfileBytes, err := util.ReadFile(c.idemixCredFile) + if err != nil { + return nil, errors.Wrapf(err, "Failed to read %s", c.idemixCredFile) + } + signerConfig := &idemixcred.SignerConfig{} + err = json.Unmarshal(credfileBytes, signerConfig) + if err != nil { + return nil, errors.Wrapf(err, "Failed to unmarshal signer config from %s", c.idemixCredFile) + } + return signerConfig, nil +} + +func (c *Client) checkX509Enrollment() error { + if c.x509EnrollmentFilesExist() { + return nil + } return fmt.Errorf("x509 enrollment information does not exist - certFile: %s keyFile: %s", c.certFile, c.keyFile) } -// checkIdemixEnrollment returns an error if CA's Idemix public key and user's -// Idemix credential does not exist and if they exist and credential verification -// fails. Returns nil if the credential verification succeeds func (c *Client) checkIdemixEnrollment() error { log.Debugf("CheckIdemixEnrollment - ipkFile: %s, idemixCredFile: %s", c.ipkFile, c.idemixCredFile) - idemixIssuerPubKeyExists := util.FileExists(c.ipkFile) - idemixCredExists := util.FileExists(c.idemixCredFile) - if idemixIssuerPubKeyExists && idemixCredExists { + if c.idemixEnrollmentFilesExist() { err := c.verifyIdemixCredential() if err != nil { return errors.WithMessage(err, "Idemix enrollment check failed") @@ -942,14 +1000,9 @@ func (c *Client) verifyIdemixCredential() error { if err != nil { return err } - credfileBytes, err := util.ReadFile(c.idemixCredFile) + signerConfig, err := c.loadIdemixSignerConfig() if err != nil { - return errors.Wrapf(err, "Failed to read %s", c.idemixCredFile) - } - signerConfig := &idemixcred.SignerConfig{} - err = json.Unmarshal(credfileBytes, signerConfig) - if err != nil { - return errors.Wrapf(err, "Failed to unmarshal signer config from %s", c.idemixCredFile) + return err } cred := new(idemix.Credential) diff --git a/lib/client/credential/idemix/credential.go b/lib/client/credential/idemix/credential.go index 52ff1e031..5113883a5 100644 --- a/lib/client/credential/idemix/credential.go +++ b/lib/client/credential/idemix/credential.go @@ -89,9 +89,33 @@ func (cred *Credential) SetVal(val interface{}) error { return nil } -// Store stores this Idemix credential to the location specified by the -// signerConfigFile attribute -func (cred *Credential) Store() error { +// SetSignerConfig sets the credential value from a SignerConfig. +// This is a cryptographic operation and does not perform file I/O. +func (cred *Credential) SetSignerConfig(val *SignerConfig) error { + cred.val = val + return nil +} + +// loadSignerConfigFromFile loads the SignerConfig from the specified file +func (cred *Credential) loadSignerConfigFromFile() (*SignerConfig, error) { + signerConfigBytes, err := util.ReadFile(cred.signerConfigFile) + if err != nil { + log.Debugf("No credential found at %s: %s", cred.signerConfigFile, err.Error()) + return nil, err + } + val := SignerConfig{} + err = json.Unmarshal(signerConfigBytes, &val) + if err != nil { + return nil, errors.Wrapf(err, fmt.Sprintf("Failed to unmarshal SignerConfig bytes from %s", cred.signerConfigFile)) + } + if val.CurveID == "" { + val.CurveID = idemix4.DefaultIdemixCurve + } + return &val, nil +} + +// storeSignerConfigToFile writes the SignerConfig to the specified file +func (cred *Credential) storeSignerConfigToFile() error { val, err := cred.Val() if err != nil { return err @@ -108,24 +132,20 @@ func (cred *Credential) Store() error { return nil } +// Store stores this Idemix credential to the location specified by the +// signerConfigFile attribute +func (cred *Credential) Store() error { + return cred.storeSignerConfigToFile() +} + // Load loads the Idemix credential from the location specified by the // signerConfigFile attribute func (cred *Credential) Load() error { - signerConfigBytes, err := util.ReadFile(cred.signerConfigFile) + val, err := cred.loadSignerConfigFromFile() if err != nil { - log.Debugf("No credential found at %s: %s", cred.signerConfigFile, err.Error()) return err } - val := SignerConfig{} - err = json.Unmarshal(signerConfigBytes, &val) - if err != nil { - return errors.Wrapf(err, fmt.Sprintf("Failed to unmarshal SignerConfig bytes from %s", cred.signerConfigFile)) - } - if val.CurveID == "" { - val.CurveID = idemix4.DefaultIdemixCurve - } - cred.val = &val - return nil + return cred.SetSignerConfig(val) } // CreateToken creates authorization token based on this Idemix credential diff --git a/lib/client/credential/x509/credential.go b/lib/client/credential/x509/credential.go index 68cd49bb1..2708c0d61 100644 --- a/lib/client/credential/x509/credential.go +++ b/lib/client/credential/x509/credential.go @@ -71,25 +71,28 @@ func (cred *Credential) EnrollmentID() (string, error) { return cred.val.GetName(), nil } -// SetVal sets *Signer for this X509 credential +// SetCredentialFromSigner sets the credential value from a Signer. +// This is a cryptographic operation and does not perform file I/O. +func (cred *Credential) SetCredentialFromSigner(signer *Signer) error { + cred.val = signer + return nil +} + +// SetVal sets *Signer for this X509 credential (implements credential.Credential interface) func (cred *Credential) SetVal(val interface{}) error { s, ok := val.(*Signer) if !ok { return errors.New("The X509 credential value must be of type *Signer for X509 credential") } - cred.val = s - return nil + return cred.SetCredentialFromSigner(s) } -// Load loads the certificate and key from the location specified by -// certFile attribute using the BCCSP of the client. The private key is -// loaded from the location specified by the keyFile attribute, if the -// private key is not found in the keystore managed by BCCSP -func (cred *Credential) Load() error { +// loadCredentialFromFiles loads the certificate and key from the specified files and returns a Signer +func (cred *Credential) loadCredentialFromFiles() (*Signer, error) { cert, err := util.ReadFile(cred.certFile) if err != nil { log.Debugf("No certificate found at %s", cred.certFile) - return err + return nil, err } csp := cred.getCSP() key, _, _, err := util.GetSignerFromCertFile(cred.certFile, csp) @@ -98,19 +101,14 @@ func (cred *Credential) Load() error { log.Debugf("No key found in the BCCSP keystore, attempting fallback") key, err = util.ImportBCCSPKeyFromPEM(cred.keyFile, csp, true) if err != nil { - return errors.WithMessage(err, fmt.Sprintf("Could not find the private key in the BCCSP keystore nor in the keyfile %s", cred.keyFile)) + return nil, errors.WithMessage(err, fmt.Sprintf("Could not find the private key in the BCCSP keystore nor in the keyfile %s", cred.keyFile)) } } - cred.val, err = NewSigner(key, cert) - if err != nil { - return err - } - return nil + return NewSigner(key, cert) } -// Store stores the certificate associated with this X509 credential to the location -// specified by certFile attribute -func (cred *Credential) Store() error { +// storeCredentialToFile writes the certificate to the specified file +func (cred *Credential) storeCredentialToFile() error { if cred.val == nil { return errors.New("X509 Credential value is not set") } @@ -122,6 +120,24 @@ func (cred *Credential) Store() error { return nil } +// Load loads the certificate and key from the location specified by +// certFile attribute using the BCCSP of the client. The private key is +// loaded from the location specified by the keyFile attribute, if the +// private key is not found in the keystore managed by BCCSP +func (cred *Credential) Load() error { + signer, err := cred.loadCredentialFromFiles() + if err != nil { + return err + } + return cred.SetCredentialFromSigner(signer) +} + +// Store stores the certificate associated with this X509 credential to the location +// specified by certFile attribute +func (cred *Credential) Store() error { + return cred.storeCredentialToFile() +} + // CreateToken creates token based on this X509 credential func (cred *Credential) CreateToken(req *http.Request, reqBody []byte) (string, error) { return util.CreateToken(cred.getCSP(), cred.val.certBytes, cred.val.key, req.Method, req.URL.RequestURI(), reqBody) diff --git a/lib/clientconfig.go b/lib/clientconfig.go index a2bfe17c3..396b17462 100644 --- a/lib/clientconfig.go +++ b/lib/clientconfig.go @@ -77,25 +77,36 @@ func (c *ClientConfig) Enroll(rawurl, home string) (*EnrollmentResponse, error) return client.Enroll(&c.Enrollment) } -// GenCSR generates a certificate signing request and writes the CSR to a file. -func (c *ClientConfig) GenCSR(home string) error { +// generateCSRInMemory generates a certificate signing request and returns the CSR in-memory. +func (c *ClientConfig) generateCSRInMemory(home string) ([]byte, error) { client := &Client{HomeDir: home, Config: c} // Generate the CSR err := client.Init() if err != nil { - return err + return nil, err } if c.CSR.CN == "" { - return errors.Errorf("CSR common name not specified; use '--csr.cn' flag") + return nil, errors.Errorf("CSR common name not specified; use '--csr.cn' flag") } csrPEM, _, err := client.GenCSR(&c.CSR, c.CSR.CN) + if err != nil { + return nil, err + } + + return csrPEM, nil +} + +// GenCSR generates a certificate signing request and writes the CSR to a file. +func (c *ClientConfig) GenCSR(home string) error { + csrPEM, err := c.generateCSRInMemory(home) if err != nil { return err } + client := &Client{HomeDir: home, Config: c} csrFile := path.Join(client.Config.MSPDir, "signcerts", fmt.Sprintf("%s.csr", c.CSR.CN)) err = util.WriteFile(csrFile, csrPEM, 0o644) if err != nil { diff --git a/lib/tls/tls.go b/lib/tls/tls.go index 419038443..1e80b538c 100644 --- a/lib/tls/tls.go +++ b/lib/tls/tls.go @@ -66,18 +66,9 @@ type KeyCertFiles struct { CertFile string `help:"PEM-encoded certificate file when mutual authenticate is enabled"` } -// GetClientTLSConfig creates a tls.Config object from certs and roots -func GetClientTLSConfig(cfg *ClientTLSConfig, csp bccsp.BCCSP) (*tls.Config, error) { +// loadClientCertsFromFiles loads client certificates from the specified files +func loadClientCertsFromFiles(cfg *ClientTLSConfig, csp bccsp.BCCSP) ([]tls.Certificate, error) { var certs []tls.Certificate - - if csp == nil { - csp = factory.GetDefault() - } - - log.Debugf("CA Files: %+v\n", cfg.CertFiles) - log.Debugf("Client Cert File: %s\n", cfg.Client.CertFile) - log.Debugf("Client Key File: %s\n", cfg.Client.KeyFile) - if cfg.Client.CertFile != "" { err := checkCertDates(cfg.Client.CertFile) if err != nil { @@ -93,6 +84,11 @@ func GetClientTLSConfig(cfg *ClientTLSConfig, csp bccsp.BCCSP) (*tls.Config, err } else { log.Debug("Client TLS certificate and/or key file not provided") } + return certs, nil +} + +// loadRootCAsFromFiles loads root CA certificates from the specified files +func loadRootCAsFromFiles(cfg *ClientTLSConfig) (*x509.CertPool, error) { rootCAPool := x509.NewCertPool() if len(cfg.CertFiles) == 0 { return nil, errors.New("No trusted root certificates for TLS were provided") @@ -108,6 +104,28 @@ func GetClientTLSConfig(cfg *ClientTLSConfig, csp bccsp.BCCSP) (*tls.Config, err return nil, errors.Errorf("Failed to process certificate from file %s", cacert) } } + return rootCAPool, nil +} + +// GetClientTLSConfig creates a tls.Config object from certs and roots +func GetClientTLSConfig(cfg *ClientTLSConfig, csp bccsp.BCCSP) (*tls.Config, error) { + if csp == nil { + csp = factory.GetDefault() + } + + log.Debugf("CA Files: %+v\n", cfg.CertFiles) + log.Debugf("Client Cert File: %s\n", cfg.Client.CertFile) + log.Debugf("Client Key File: %s\n", cfg.Client.KeyFile) + + certs, err := loadClientCertsFromFiles(cfg, csp) + if err != nil { + return nil, err + } + + rootCAPool, err := loadRootCAsFromFiles(cfg) + if err != nil { + return nil, err + } config := &tls.Config{ Certificates: certs, diff --git a/lib/util.go b/lib/util.go index 7014f790c..20c093c01 100644 --- a/lib/util.go +++ b/lib/util.go @@ -45,10 +45,9 @@ func BytesToX509Cert(bytes []byte) (*x509.Certificate, error) { return cert, err } -// LoadPEMCertPool loads a pool of PEM certificates from list of files -func LoadPEMCertPool(certFiles []string) (*x509.CertPool, error) { - certPool := x509.NewCertPool() - +// loadPEMCertsFromFiles loads PEM certificates from a list of files and returns a slice of byte slices +func loadPEMCertsFromFiles(certFiles []string) ([][]byte, error) { + var certs [][]byte if len(certFiles) > 0 { for _, cert := range certFiles { log.Debugf("Reading cert file: %s", cert) @@ -56,11 +55,25 @@ func LoadPEMCertPool(certFiles []string) (*x509.CertPool, error) { if err != nil { return nil, err } + certs = append(certs, pemCerts) + } + } + return certs, nil +} - log.Debugf("Appending cert %s to pool", cert) - if !certPool.AppendCertsFromPEM(pemCerts) { - return nil, errors.New("Failed to load cert pool") - } +// LoadPEMCertPool loads a pool of PEM certificates from list of files +func LoadPEMCertPool(certFiles []string) (*x509.CertPool, error) { + certPool := x509.NewCertPool() + + certs, err := loadPEMCertsFromFiles(certFiles) + if err != nil { + return nil, err + } + + for i, pemCerts := range certs { + log.Debugf("Appending cert %s to pool", certFiles[i]) + if !certPool.AppendCertsFromPEM(pemCerts) { + return nil, errors.New("Failed to load cert pool") } } From bf716f732fbc88aeac81f3becc5b6c520dd6f6ef Mon Sep 17 00:00:00 2001 From: Jacob Barzee Date: Sat, 3 Jan 2026 10:36:11 -0800 Subject: [PATCH 2/2] allow custom http clients --- lib/client.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/client.go b/lib/client.go index e72eb543d..6ad2ad39c 100644 --- a/lib/client.go +++ b/lib/client.go @@ -157,7 +157,19 @@ func (c *Client) Init() error { return nil } +// SetHTTPClient sets a custom HTTP client for this Fabric CA client. +// Call this before Init() to prevent default HTTP client creation, or after +// Init() to override the default HTTP client. +func (c *Client) SetHTTPClient(client *http.Client) { + c.httpClient = client +} + func (c *Client) initHTTPClient() error { + // Skip initialization if HTTP client was already set + if c.httpClient != nil { + return nil + } + tr := new(http.Transport) if c.Config.TLS.Enabled { log.Info("TLS Enabled")