mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-19 08:30:49 +02:00
add coverage for cert subcommand
This commit is contained in:
parent
4ae57e83b3
commit
eba14089f6
119
cmd/cert.go
119
cmd/cert.go
@ -14,6 +14,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
@ -25,43 +26,55 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CmdCert represents the available cert sub-command.
|
// CmdCert represents the available cert sub-command.
|
||||||
var CmdCert = &cli.Command{
|
func CmdCert() *cli.Command {
|
||||||
Name: "cert",
|
return &cli.Command{
|
||||||
Usage: "Generate self-signed certificate",
|
Name: "cert",
|
||||||
Description: `Generate a self-signed X.509 certificate for a TLS server.
|
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.`,
|
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||||
Action: runCert,
|
Action: runCert,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "host",
|
Name: "host",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Comma-separated hostnames and IPs to generate a certificate for",
|
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 {
|
func publicKey(priv any) any {
|
||||||
@ -109,17 +122,19 @@ func runCert(_ context.Context, c *cli.Command) error {
|
|||||||
case "P521":
|
case "P521":
|
||||||
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||||
default:
|
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 {
|
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
|
var notBefore time.Time
|
||||||
if startDate := c.String("start-date"); startDate != "" {
|
if startDate := c.String("start-date"); startDate != "" {
|
||||||
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
|
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
notBefore = time.Now()
|
notBefore = time.Now()
|
||||||
@ -130,7 +145,8 @@ func runCert(_ context.Context, c *cli.Command) error {
|
|||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
if err != nil {
|
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{
|
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)
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
||||||
if err != nil {
|
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 {
|
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})
|
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to encode certificate: %v", err)
|
// log.Fatalf("Failed to encode certificate: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
err = certOut.Close()
|
err = certOut.Close()
|
||||||
if err != nil {
|
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")
|
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 {
|
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))
|
err = pem.Encode(keyOut, pemBlockForKey(priv))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to encode key: %v", err)
|
// log.Fatalf("Failed to encode key: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
err = keyOut.Close()
|
err = keyOut.Close()
|
||||||
if err != nil {
|
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")
|
log.Println("Written key.pem")
|
||||||
return nil
|
return nil
|
||||||
|
125
cmd/cert_test.go
Normal file
125
cmd/cert_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
|
||||||
subCmdStandalone := []*cli.Command{
|
subCmdStandalone := []*cli.Command{
|
||||||
CmdCert,
|
CmdCert(),
|
||||||
CmdGenerate,
|
CmdGenerate,
|
||||||
CmdDocs,
|
CmdDocs,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user