From 3f1a530de900e0d0fc65923aaad4114e79fc93a0 Mon Sep 17 00:00:00 2001 From: aliamerj Date: Wed, 11 Jun 2025 10:27:42 +0300 Subject: [PATCH] add intermediate-file flag to separate leaf and intermediate certs --- command/ca/certificate.go | 42 +++++++++++++++++++++++++++-- utils/cautils/certificate_flow.go | 44 ++++++++++++++++++++++--------- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/command/ca/certificate.go b/command/ca/certificate.go index 5657d66e0..d29356909 100644 --- a/command/ca/certificate.go +++ b/command/ca/certificate.go @@ -175,6 +175,10 @@ multiple SANs. The '--san' flag and the '--token' flag are mutually exclusive.`, Usage: "The directory where TPM keys and certificates will be stored", Value: filepath.Join(step.Path(), "tpm"), }, + cli.StringFlag{ + Name: "intermediate-file", + Usage: "Write intermediate certificates to a separate file", + }, flags.TemplateSet, flags.TemplateSetFile, flags.CaConfig, @@ -279,8 +283,42 @@ func certificateAction(ctx *cli.Context) error { return errors.New("token is not supported") } - if err := flow.Sign(ctx, tok, req.CsrPEM, crtFile); err != nil { - return err + if intermediateFile := ctx.String("intermediate-file"); intermediateFile != "" { + certs, err := flow.SignReturnsCerts(ctx, tok, req.CsrPEM) + if err != nil { + return err + } + + if len(certs) < 2 { + return errors.New("certificate chain must contain at least 2 certificates (leaf + intermediate)") + } + + // Leaf cert should NOT be a CA + if certs[0].IsCA { + return errors.New("first certificate (leaf) in chain should not be a CA certificate") + } + + // Intermediates should be CAs + for i := 1; i < len(certs); i++ { + if !certs[i].IsCA { + return errors.New("intermediate certificate is not a CA certificate") + } + } + + // Write leaf cert + if err := cautils.WriteCerts(crtFile, certs[:1]); err != nil { + return errors.Wrap(err, "error writing leaf certificate") + } + // Write intermediates + if err := cautils.WriteCerts(intermediateFile, certs[1:]); err != nil { + return errors.Wrap(err, "error writing intermediate certificates") + } + ui.PrintSelected("Intermediate Chain", intermediateFile) + + } else { + if err := flow.Sign(ctx, tok, req.CsrPEM, crtFile); err != nil { + return err + } } _, err = pemutil.Serialize(pk, pemutil.ToFile(keyFile, 0600)) diff --git a/utils/cautils/certificate_flow.go b/utils/cautils/certificate_flow.go index 5e25ba73a..a2514c79d 100644 --- a/utils/cautils/certificate_flow.go +++ b/utils/cautils/certificate_flow.go @@ -247,21 +247,30 @@ func (f *CertificateFlow) GenerateIdentityToken(ctx *cli.Context) (string, error // Sign signs the CSR using the online or the offline certificate authority. func (f *CertificateFlow) Sign(ctx *cli.Context, tok string, csr api.CertificateRequest, crtFile string) error { - client, err := f.GetClient(ctx, tok) + certs, err := f.SignReturnsCerts(ctx, tok, csr) if err != nil { return err } + return WriteCerts(crtFile, certs) +} + +// SignReturnsCerts signs the CSR and returns the certificates +func (f *CertificateFlow) SignReturnsCerts(ctx *cli.Context, tok string, csr api.CertificateRequest) ([]*x509.Certificate, error) { + client, err := f.GetClient(ctx, tok) + if err != nil { + return nil, err + } // parse times or durations notBefore, notAfter, err := flags.ParseTimeDuration(ctx) if err != nil { - return err + return nil, err } // parse template data templateData, err := flags.ParseTemplateData(ctx) if err != nil { - return err + return nil, err } req := &api.SignRequest{ @@ -274,21 +283,18 @@ func (f *CertificateFlow) Sign(ctx *cli.Context, tok string, csr api.Certificate resp, err := client.Sign(req) if err != nil { - return err + return nil, err } if len(resp.CertChainPEM) == 0 { resp.CertChainPEM = []api.Certificate{resp.ServerPEM, resp.CaPEM} } - var data []byte - for _, certPEM := range resp.CertChainPEM { - pemblk, err := pemutil.Serialize(certPEM.Certificate) - if err != nil { - return errors.Wrap(err, "error serializing from step-ca API response") - } - data = append(data, pem.EncodeToMemory(pemblk)...) + certs := make([]*x509.Certificate, len(resp.CertChainPEM)) + + for i, certPEM := range resp.CertChainPEM { + certs[i] = certPEM.Certificate } - return utils.WriteFile(crtFile, data, 0600) + return certs, nil } // CreateSignRequest is a helper function that given an x509 OTT returns a @@ -418,3 +424,17 @@ func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP, email } return x509util.SplitSANs(unique) } + +// writeCerts writes one or more certificates to a file using the standard overwrite protection +func WriteCerts(filename string, certs []*x509.Certificate) error { + var data []byte + for _, certPEM := range certs { + pemblk, err := pemutil.Serialize(certPEM) + if err != nil { + return errors.Wrap(err, "error serializing from step-ca API response") + } + data = append(data, pem.EncodeToMemory(pemblk)...) + } + + return utils.WriteFile(filename, data, 0600) +}