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
6package cmd
7
8import (
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.
27var 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.
31Outputs 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
66func 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
77func 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
92func 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: "Forgejo",
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}