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  }