0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-07-18 02:12:53 +02:00

add coverage for cert subcommand

This commit is contained in:
TheFox0x7 2025-05-25 00:16:48 +02:00
parent 4ae57e83b3
commit eba14089f6
No known key found for this signature in database
GPG Key ID: 6CA33903484AF7C2
3 changed files with 197 additions and 49 deletions

View File

@ -14,6 +14,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
@ -25,43 +26,55 @@ import (
)
// CmdCert represents the available cert sub-command.
var CmdCert = &cli.Command{
Name: "cert",
Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server.
func CmdCert() *cli.Command {
return &cli.Command{
Name: "cert",
Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server.
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Action: runCert,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
Action: runCert,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
},
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
&cli.StringFlag{
Name: "out",
Value: "cert.pem",
Usage: "Path to the file where there certificate will be saved",
},
&cli.StringFlag{
Name: "keyout",
Value: "key.pem",
Usage: "Path to the file where there certificate key will be saved",
},
},
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
},
}
}
func publicKey(priv any) any {
@ -109,17 +122,19 @@ func runCert(_ context.Context, c *cli.Command) error {
case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
err = fmt.Errorf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
}
if err != nil {
log.Fatalf("Failed to generate private key: %v", err)
// log.Fatalf("Failed to generate private key: %v", err)
return err
}
var notBefore time.Time
if startDate := c.String("start-date"); startDate != "" {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
if err != nil {
log.Fatalf("Failed to parse creation date: %v", err)
// log.Fatalf("Failed to parse creation date: %v", err)
return fmt.Errorf("Failed to parse creation date %w", err)
}
} else {
notBefore = time.Now()
@ -130,7 +145,8 @@ func runCert(_ context.Context, c *cli.Command) error {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Failed to generate serial number: %v", err)
// log.Fatalf("Failed to generate serial number: %v", err)
return err
}
template := x509.Certificate{
@ -163,34 +179,41 @@ func runCert(_ context.Context, c *cli.Command) error {
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
// log.Fatalf("Failed to create certificate: %v", err)
return err
}
certOut, err := os.Create("cert.pem")
certOut, err := os.Create(c.String("out"))
if err != nil {
log.Fatalf("Failed to open cert.pem for writing: %v", err)
// log.Fatalf("Failed to open cert.pem for writing: %v", err)
return err
}
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
log.Fatalf("Failed to encode certificate: %v", err)
// log.Fatalf("Failed to encode certificate: %v", err)
return err
}
err = certOut.Close()
if err != nil {
log.Fatalf("Failed to write cert: %v", err)
// log.Fatalf("Failed to write cert: %v", err)
return err
}
log.Println("Written cert.pem")
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
log.Fatalf("Failed to open key.pem for writing: %v", err)
// log.Fatalf("Failed to open key.pem for writing: %v", err)
return err
}
err = pem.Encode(keyOut, pemBlockForKey(priv))
if err != nil {
log.Fatalf("Failed to encode key: %v", err)
// log.Fatalf("Failed to encode key: %v", err)
return err
}
err = keyOut.Close()
if err != nil {
log.Fatalf("Failed to write key: %v", err)
// log.Fatalf("Failed to write key: %v", err)
return err
}
log.Println("Written key.pem")
return nil

125
cmd/cert_test.go Normal file
View File

@ -0,0 +1,125 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCertCommand(t *testing.T) {
cases := []struct {
name string
args []string
}{
{
name: "RSA cert generation",
args: []string{
"cert-test",
"--host", "localhost",
"--rsa-bits", "2048",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
{
name: "ECDSA cert generation",
args: []string{
"cert-test",
"--host", "localhost",
"--ecdsa-curve", "P256",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
{
name: "mixed host, certificate authority",
args: []string{
"cert-test",
"--host", "localhost,127.0.0.1",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
app := CmdCert()
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
keyFile := filepath.Join(tempDir, "key.pem")
args := append(c.args, "--out", certFile, "--keyout", keyFile)
err := app.Run(t.Context(), args)
require.NoError(t, err)
assert.FileExists(t, certFile)
assert.FileExists(t, keyFile)
})
}
}
func TestCertCommandFailures(t *testing.T) {
cases := []struct {
name string
args []string
errMsg string
}{
{
name: "Start Date Parsing failure",
args: []string{
"cert-test",
"--host", "localhost",
"--start-date", "invalid-date",
},
errMsg: "parsing time",
},
{
name: "Unknown curve",
args: []string{
"cert-test",
"--host", "localhost",
"--ecdsa-curve", "invalid-curve",
},
errMsg: "Unrecognized elliptic curve",
},
{
name: "Key generation failure",
args: []string{
"cert-test",
"--host", "localhost",
"--rsa-bits", "invalid-bits",
},
},
{
name: "Missing parameters",
args: []string{
"cert-test",
},
errMsg: "host is not set",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
app := CmdCert()
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
keyFile := filepath.Join(tempDir, "key.pem")
args := append(c.args, "--out", certFile, "--keyout", keyFile)
err := app.Run(t.Context(), args)
require.Error(t, err)
if c.errMsg != "" {
assert.ErrorContains(t, err, c.errMsg)
}
assert.NoFileExists(t, certFile)
assert.NoFileExists(t, keyFile)
})
}
}

View File

@ -150,7 +150,7 @@ func NewMainApp(appVer AppVersion) *cli.Command {
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
CmdCert,
CmdCert(),
CmdGenerate,
CmdDocs,
}