github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/cmd/server/commands/generate_cert.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package commands 20 21 import ( 22 "crypto/ecdsa" 23 "crypto/ed25519" 24 "crypto/elliptic" 25 "crypto/rand" 26 "crypto/rsa" 27 "crypto/x509" 28 "crypto/x509/pkix" 29 "encoding/pem" 30 "fmt" 31 "math/big" 32 "net" 33 "os" 34 "strings" 35 "time" 36 37 "github.com/spf13/cobra" 38 ) 39 40 var ( 41 host *string = new(string) 42 validFrom *string = new(string) 43 validFor *time.Duration = new(time.Duration) 44 isCA *bool = new(bool) 45 rsaBits *int = new(int) 46 ecdsaCurve *string = new(string) 47 ed25519Key *bool = new(bool) 48 49 generateCertCmd = &cobra.Command{ 50 Use: "cert", 51 Short: "Generate a self-signed X.509 certificate for a TLS server. Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.", 52 Run: func(cmd *cobra.Command, args []string) { 53 54 if len(*host) == 0 { 55 log.Fatalf("Missing required --host parameter") 56 } 57 58 var priv any 59 var err error 60 switch *ecdsaCurve { 61 case "": 62 if *ed25519Key { 63 _, priv, err = ed25519.GenerateKey(rand.Reader) 64 } else { 65 priv, err = rsa.GenerateKey(rand.Reader, *rsaBits) 66 } 67 case "P224": 68 priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) 69 case "P256": 70 priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 71 case "P384": 72 priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) 73 case "P521": 74 priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) 75 default: 76 log.Fatalf("Unrecognized elliptic curve: %q", *ecdsaCurve) 77 } 78 if err != nil { 79 log.Fatalf("Failed to generate private key: %v", err) 80 } 81 82 // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature 83 // KeyUsage bits set in the x509.Certificate template 84 keyUsage := x509.KeyUsageDigitalSignature 85 // Only RSA subject keys should have the KeyEncipherment KeyUsage bits set. In 86 // the context of TLS this KeyUsage is particular to RSA key exchange and 87 // authentication. 88 if _, isRSA := priv.(*rsa.PrivateKey); isRSA { 89 keyUsage |= x509.KeyUsageKeyEncipherment 90 } 91 92 var notBefore time.Time 93 if len(*validFrom) == 0 { 94 notBefore = time.Now() 95 } else { 96 notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom) 97 if err != nil { 98 log.Fatalf("Failed to parse creation date: %v", err) 99 } 100 } 101 102 notAfter := notBefore.Add(*validFor) 103 104 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 105 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 106 if err != nil { 107 log.Fatalf("Failed to generate serial number: %v", err) 108 } 109 110 template := x509.Certificate{ 111 SerialNumber: serialNumber, 112 Subject: pkix.Name{ 113 Organization: []string{"Acme Co"}, 114 }, 115 NotBefore: notBefore, 116 NotAfter: notAfter, 117 118 KeyUsage: keyUsage, 119 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 120 BasicConstraintsValid: true, 121 } 122 123 hosts := strings.Split(*host, ",") 124 for _, h := range hosts { 125 if ip := net.ParseIP(h); ip != nil { 126 template.IPAddresses = append(template.IPAddresses, ip) 127 } else { 128 template.DNSNames = append(template.DNSNames, h) 129 } 130 } 131 132 if *isCA { 133 template.IsCA = true 134 template.KeyUsage |= x509.KeyUsageCertSign 135 } 136 137 derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) 138 if err != nil { 139 log.Fatalf("Failed to create certificate: %v", err) 140 } 141 142 certOut, err := os.Create("cert.pem") 143 if err != nil { 144 log.Fatalf("Failed to open cert.pem for writing: %v", err) 145 } 146 if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 147 log.Fatalf("Failed to write data to cert.pem: %v", err) 148 } 149 if err := certOut.Close(); err != nil { 150 log.Fatalf("Error closing cert.pem: %v", err) 151 } 152 fmt.Print("wrote cert.pem\n") 153 154 keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 155 if err != nil { 156 log.Fatalf("Failed to open key.pem for writing: %v", err) 157 } 158 privBytes, err := x509.MarshalPKCS8PrivateKey(priv) 159 if err != nil { 160 log.Fatalf("Unable to marshal private key: %v", err) 161 } 162 if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { 163 log.Fatalf("Failed to write data to key.pem: %v", err) 164 } 165 if err := keyOut.Close(); err != nil { 166 log.Fatalf("Error closing key.pem: %v", err) 167 } 168 fmt.Print("wrote key.pem\n") 169 170 }, 171 } 172 ) 173 174 func publicKey(priv any) any { 175 switch k := priv.(type) { 176 case *rsa.PrivateKey: 177 return &k.PublicKey 178 case *ecdsa.PrivateKey: 179 return &k.PublicKey 180 case ed25519.PrivateKey: 181 return k.Public().(ed25519.PublicKey) 182 default: 183 return nil 184 } 185 } 186 187 func init() { 188 generateCertCmd.Flags().StringVar(host, "host", "localhost", "Comma-separated hostnames and IPs to generate a certificate for") 189 generateCertCmd.Flags().StringVar(validFrom, "start-date", "", "Creation date formatted as Jan 1 15:04:05 2011") 190 generateCertCmd.Flags().DurationVar(validFor, "duration", 365*24*time.Hour, "Duration that certificate is valid for") 191 generateCertCmd.Flags().BoolVar(isCA, "ca", false, "whether this cert should be its own Certificate Authority") 192 generateCertCmd.Flags().IntVar(rsaBits, "rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set") 193 generateCertCmd.Flags().StringVar(ecdsaCurve, "ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521") 194 generateCertCmd.Flags().BoolVar(ed25519Key, "ed25519", false, "Generate an Ed25519 key") 195 }