From 2ce3a68e41d678ec6d1fb02bd2481dd7315bb8df Mon Sep 17 00:00:00 2001 From: Cyril Galibern Date: Tue, 20 Jan 2026 12:54:40 +0100 Subject: [PATCH] Add support for generating and formatting certificate signing requests (CSR) - Implement `GenCertificateSigningRequest` method for keystore objects. - Add `newCmdObjectCertificateSigningRequest` for CSR command registration. - Extend certificate subject to include locality and province fields. - Introduce `CmdObjectCertificateSigningRequest` for CSR creation command line execution. - Register CSR commands for `usr` and `sec` object kinds. --- core/object/datastore.go | 1 + core/object/sec_gencert.go | 22 ++++++++++ core/om/factory.go | 15 +++++++ core/om/kind_sec.go | 1 + core/om/kind_usr.go | 1 + .../object_certificate_signing_request.go | 42 +++++++++++++++++++ 6 files changed, 82 insertions(+) create mode 100644 core/omcmd/object_certificate_signing_request.go diff --git a/core/object/datastore.go b/core/object/datastore.go index abb29b2bf..f391a57c1 100644 --- a/core/object/datastore.go +++ b/core/object/datastore.go @@ -52,6 +52,7 @@ type ( // KeyStore is implemented by encrypting KeyStore object kinds (usr, sec). KeyStore interface { GenCert() error + GenCertificateSigningRequest() ([]byte, error) PKCS(password []byte) ([]byte, error) } ) diff --git a/core/object/sec_gencert.go b/core/object/sec_gencert.go index a90244765..960631062 100644 --- a/core/object/sec_gencert.go +++ b/core/object/sec_gencert.go @@ -32,6 +32,26 @@ func (t *sec) GenCert() error { return t.config.Commit() } +// GenCertificateSigningRequest generates a certificate signing request. It also creates a private key if needed. +func (t *sec) GenCertificateSigningRequest() ([]byte, error) { + privateKey, err := t.getPriv() + if err != nil { + return nil, err + } + + csrTemplate := x509.CertificateRequest{ + Subject: t.subject(), + SignatureAlgorithm: x509.SHA256WithRSA, + } + + if csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privateKey); err != nil { + return nil, err + } else { + csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) + return csrPEM, t.config.Commit() + } +} + func (t *sec) genSelfSigned() error { t.log.Tracef("generate a self-signed certificate") priv, err := t.getPriv() @@ -176,6 +196,8 @@ func (t *sec) subject() pkix.Name { Country: []string{t.CertInfo("c")}, Organization: []string{t.CertInfo("o")}, OrganizationalUnit: []string{t.CertInfo("ou")}, + Locality: []string{t.CertInfo("l")}, + Province: []string{t.CertInfo("st")}, CommonName: t.CertInfo("cn"), } } diff --git a/core/om/factory.go b/core/om/factory.go index 056851953..569ea86d4 100644 --- a/core/om/factory.go +++ b/core/om/factory.go @@ -1517,6 +1517,21 @@ func newCmdObjectCertificateCreate(kind string) *cobra.Command { return cmd } +func newCmdObjectCertificateSigningRequest(kind string) *cobra.Command { + var options commands.CmdObjectCertificateSigningRequest + cmd := &cobra.Command{ + Use: "signing-request", + Aliases: []string{"sr"}, + Short: "format a certificate signing request", + RunE: func(cmd *cobra.Command, args []string) error { + return options.Run(kind) + }, + } + flags := cmd.Flags() + addFlagsGlobal(flags, &options.OptsGlobal) + return cmd +} + func newCmdObjectCertificatePKCS(kind string) *cobra.Command { var options commands.CmdObjectCertificatePKCS cmd := &cobra.Command{ diff --git a/core/om/kind_sec.go b/core/om/kind_sec.go index ec801c7d5..19ab82d76 100644 --- a/core/om/kind_sec.go +++ b/core/om/kind_sec.go @@ -55,6 +55,7 @@ func init() { ) cmdObjectCertificate.AddCommand( newCmdObjectCertificateCreate(kind), + newCmdObjectCertificateSigningRequest(kind), newCmdObjectCertificatePKCS(kind), ) cmdObjectConfig.AddCommand( diff --git a/core/om/kind_usr.go b/core/om/kind_usr.go index a96876788..26ab2ff18 100644 --- a/core/om/kind_usr.go +++ b/core/om/kind_usr.go @@ -53,6 +53,7 @@ func init() { ) cmdObjectCertificate.AddCommand( newCmdObjectCertificateCreate(kind), + newCmdObjectCertificateSigningRequest(kind), newCmdObjectCertificatePKCS(kind), ) cmdObjectConfig.AddCommand( diff --git a/core/omcmd/object_certificate_signing_request.go b/core/omcmd/object_certificate_signing_request.go new file mode 100644 index 000000000..9be9e44de --- /dev/null +++ b/core/omcmd/object_certificate_signing_request.go @@ -0,0 +1,42 @@ +package omcmd + +import ( + "context" + "fmt" + + "github.com/opensvc/om3/v3/core/commoncmd" + "github.com/opensvc/om3/v3/core/naming" + "github.com/opensvc/om3/v3/core/object" + "github.com/opensvc/om3/v3/core/objectaction" +) + +type ( + CmdObjectCertificateSigningRequest struct { + OptsGlobal + } +) + +func (t *CmdObjectCertificateSigningRequest) Run(kind string) error { + mergedSelector := commoncmd.MergeSelector("", t.ObjectSelector, kind, "") + return objectaction.New( + objectaction.WithColor(t.Color), + objectaction.WithOutput(t.Output), + objectaction.WithObjectSelector(mergedSelector), + objectaction.WithLocalFunc(func(ctx context.Context, p naming.Path) (interface{}, error) { + o, err := object.New(p) + if err != nil { + return nil, err + } + store, ok := o.(object.KeyStore) + if !ok { + return nil, fmt.Errorf("%s is not a keystore", p) + } + if b, err := store.GenCertificateSigningRequest(); err != nil { + return nil, fmt.Errorf("%s can't create certificate signing request: %w", p, err) + } else { + fmt.Println(string(b)) + return nil, nil + } + }), + ).Do() +}