code.gitea.io/gitea@v1.21.7/cmd/cert.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Copyright 2014 The Gogs Authors. All rights reserved.
     3  // Copyright 2016 The Gitea Authors. All rights reserved.
     4  // SPDX-License-Identifier: MIT
     5  
     6  package cmd
     7  
     8  import (
     9  	"crypto/ecdsa"
    10  	"crypto/elliptic"
    11  	"crypto/rand"
    12  	"crypto/rsa"
    13  	"crypto/x509"
    14  	"crypto/x509/pkix"
    15  	"encoding/pem"
    16  	"log"
    17  	"math/big"
    18  	"net"
    19  	"os"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/urfave/cli/v2"
    24  )
    25  
    26  // CmdCert represents the available cert sub-command.
    27  var CmdCert = &cli.Command{
    28  	Name:  "cert",
    29  	Usage: "Generate self-signed certificate",
    30  	Description: `Generate a self-signed X.509 certificate for a TLS server.
    31  Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
    32  	Action: runCert,
    33  	Flags: []cli.Flag{
    34  		&cli.StringFlag{
    35  			Name:  "host",
    36  			Value: "",
    37  			Usage: "Comma-separated hostnames and IPs to generate a certificate for",
    38  		},
    39  		&cli.StringFlag{
    40  			Name:  "ecdsa-curve",
    41  			Value: "",
    42  			Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
    43  		},
    44  		&cli.IntFlag{
    45  			Name:  "rsa-bits",
    46  			Value: 3072,
    47  			Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
    48  		},
    49  		&cli.StringFlag{
    50  			Name:  "start-date",
    51  			Value: "",
    52  			Usage: "Creation date formatted as Jan 1 15:04:05 2011",
    53  		},
    54  		&cli.DurationFlag{
    55  			Name:  "duration",
    56  			Value: 365 * 24 * time.Hour,
    57  			Usage: "Duration that certificate is valid for",
    58  		},
    59  		&cli.BoolFlag{
    60  			Name:  "ca",
    61  			Usage: "whether this cert should be its own Certificate Authority",
    62  		},
    63  	},
    64  }
    65  
    66  func publicKey(priv any) any {
    67  	switch k := priv.(type) {
    68  	case *rsa.PrivateKey:
    69  		return &k.PublicKey
    70  	case *ecdsa.PrivateKey:
    71  		return &k.PublicKey
    72  	default:
    73  		return nil
    74  	}
    75  }
    76  
    77  func pemBlockForKey(priv any) *pem.Block {
    78  	switch k := priv.(type) {
    79  	case *rsa.PrivateKey:
    80  		return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
    81  	case *ecdsa.PrivateKey:
    82  		b, err := x509.MarshalECPrivateKey(k)
    83  		if err != nil {
    84  			log.Fatalf("Unable to marshal ECDSA private key: %v", err)
    85  		}
    86  		return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
    87  	default:
    88  		return nil
    89  	}
    90  }
    91  
    92  func runCert(c *cli.Context) error {
    93  	if err := argsSet(c, "host"); err != nil {
    94  		return err
    95  	}
    96  
    97  	var priv any
    98  	var err error
    99  	switch c.String("ecdsa-curve") {
   100  	case "":
   101  		priv, err = rsa.GenerateKey(rand.Reader, c.Int("rsa-bits"))
   102  	case "P224":
   103  		priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
   104  	case "P256":
   105  		priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   106  	case "P384":
   107  		priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
   108  	case "P521":
   109  		priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
   110  	default:
   111  		log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
   112  	}
   113  	if err != nil {
   114  		log.Fatalf("Failed to generate private key: %v", err)
   115  	}
   116  
   117  	var notBefore time.Time
   118  	if startDate := c.String("start-date"); startDate != "" {
   119  		notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
   120  		if err != nil {
   121  			log.Fatalf("Failed to parse creation date: %v", err)
   122  		}
   123  	} else {
   124  		notBefore = time.Now()
   125  	}
   126  
   127  	notAfter := notBefore.Add(c.Duration("duration"))
   128  
   129  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   130  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
   131  	if err != nil {
   132  		log.Fatalf("Failed to generate serial number: %v", err)
   133  	}
   134  
   135  	template := x509.Certificate{
   136  		SerialNumber: serialNumber,
   137  		Subject: pkix.Name{
   138  			Organization: []string{"Acme Co"},
   139  			CommonName:   "Gitea",
   140  		},
   141  		NotBefore: notBefore,
   142  		NotAfter:  notAfter,
   143  
   144  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   145  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   146  		BasicConstraintsValid: true,
   147  	}
   148  
   149  	hosts := strings.Split(c.String("host"), ",")
   150  	for _, h := range hosts {
   151  		if ip := net.ParseIP(h); ip != nil {
   152  			template.IPAddresses = append(template.IPAddresses, ip)
   153  		} else {
   154  			template.DNSNames = append(template.DNSNames, h)
   155  		}
   156  	}
   157  
   158  	if c.Bool("ca") {
   159  		template.IsCA = true
   160  		template.KeyUsage |= x509.KeyUsageCertSign
   161  	}
   162  
   163  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
   164  	if err != nil {
   165  		log.Fatalf("Failed to create certificate: %v", err)
   166  	}
   167  
   168  	certOut, err := os.Create("cert.pem")
   169  	if err != nil {
   170  		log.Fatalf("Failed to open cert.pem for writing: %v", err)
   171  	}
   172  	err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
   173  	if err != nil {
   174  		log.Fatalf("Failed to encode certificate: %v", err)
   175  	}
   176  	err = certOut.Close()
   177  	if err != nil {
   178  		log.Fatalf("Failed to write cert: %v", err)
   179  	}
   180  	log.Println("Written cert.pem")
   181  
   182  	keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
   183  	if err != nil {
   184  		log.Fatalf("Failed to open key.pem for writing: %v", err)
   185  	}
   186  	err = pem.Encode(keyOut, pemBlockForKey(priv))
   187  	if err != nil {
   188  		log.Fatalf("Failed to encode key: %v", err)
   189  	}
   190  	err = keyOut.Close()
   191  	if err != nil {
   192  		log.Fatalf("Failed to write key: %v", err)
   193  	}
   194  	log.Println("Written key.pem")
   195  	return nil
   196  }