From 6c6d1f4393fed701d28d9620ab2f90e7d15e34bf Mon Sep 17 00:00:00 2001 From: Aaron Gable Date: Fri, 8 Dec 2023 10:01:38 -0800 Subject: [PATCH] Add full support for ECDSA roots (#50) --- go.mod | 2 ++ main.go | 67 +++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 0f868b4..d20f8af 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/jsha/minica + +go 1.15 diff --git a/main.go b/main.go index 9734969..30d357c 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,8 @@ package main import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha1" @@ -36,15 +38,15 @@ type issuer struct { cert *x509.Certificate } -func getIssuer(keyFile, certFile string) (*issuer, error) { +func getIssuer(keyFile, certFile string, alg x509.PublicKeyAlgorithm) (*issuer, error) { keyContents, keyErr := ioutil.ReadFile(keyFile) certContents, certErr := ioutil.ReadFile(certFile) if os.IsNotExist(keyErr) && os.IsNotExist(certErr) { - err := makeIssuer(keyFile, certFile) + err := makeIssuer(keyFile, certFile, alg) if err != nil { return nil, err } - return getIssuer(keyFile, certFile) + return getIssuer(keyFile, certFile, alg) } else if keyErr != nil { return nil, fmt.Errorf("%s (but %s exists)", keyErr, certFile) } else if certErr != nil { @@ -74,10 +76,25 @@ func readPrivateKey(keyContents []byte) (crypto.Signer, error) { block, _ := pem.Decode(keyContents) if block == nil { return nil, fmt.Errorf("no PEM found") - } else if block.Type != "RSA PRIVATE KEY" && block.Type != "ECDSA PRIVATE KEY" { - return nil, fmt.Errorf("incorrect PEM type %s", block.Type) + } else if block.Type == "PRIVATE KEY" { + signer, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse PKCS8: %w", err) + } + switch t := signer.(type) { + case *rsa.PrivateKey: + return signer.(*rsa.PrivateKey), nil + case *ecdsa.PrivateKey: + return signer.(*ecdsa.PrivateKey), nil + default: + return nil, fmt.Errorf("unsupported PKCS8 key type: %t", t) + } + } else if block.Type == "RSA PRIVATE KEY" { + return x509.ParsePKCS1PrivateKey(block.Bytes) + } else if block.Type == "EC PRIVATE KEY" || block.Type == "ECDSA PRIVATE KEY" { + return x509.ParseECPrivateKey(block.Bytes) } - return x509.ParsePKCS1PrivateKey(block.Bytes) + return nil, fmt.Errorf("incorrect PEM type %s", block.Type) } func readCert(certContents []byte) (*x509.Certificate, error) { @@ -90,8 +107,8 @@ func readCert(certContents []byte) (*x509.Certificate, error) { return x509.ParseCertificate(block.Bytes) } -func makeIssuer(keyFile, certFile string) error { - key, err := makeKey(keyFile) +func makeIssuer(keyFile, certFile string, alg x509.PublicKeyAlgorithm) error { + key, err := makeKey(keyFile, alg) if err != nil { return err } @@ -102,12 +119,22 @@ func makeIssuer(keyFile, certFile string) error { return nil } -func makeKey(filename string) (*rsa.PrivateKey, error) { - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err +func makeKey(filename string, alg x509.PublicKeyAlgorithm) (crypto.Signer, error) { + var key crypto.Signer + var err error + switch { + case alg == x509.RSA: + key, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + case alg == x509.ECDSA: + key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, err + } } - der := x509.MarshalPKCS1PrivateKey(key) + der, err := x509.MarshalPKCS8PrivateKey(key) if err != nil { return nil, err } @@ -117,7 +144,7 @@ func makeKey(filename string) (*rsa.PrivateKey, error) { } defer file.Close() err = pem.Encode(file, &pem.Block{ - Type: "RSA PRIVATE KEY", + Type: "PRIVATE KEY", Bytes: der, }) if err != nil { @@ -227,7 +254,7 @@ func sign(iss *issuer, domains []string, ipAddresses []string) (*x509.Certificat if err != nil && !os.IsExist(err) { return nil, err } - key, err := makeKey(fmt.Sprintf("%s/key.pem", cnFolder)) + key, err := makeKey(fmt.Sprintf("%s/key.pem", cnFolder), x509.RSA) if err != nil { return nil, err } @@ -287,6 +314,7 @@ func split(s string) (results []string) { func main2() error { var caKey = flag.String("ca-key", "minica-key.pem", "Root private key filename, PEM encoded.") var caCert = flag.String("ca-cert", "minica.pem", "Root certificate filename, PEM encoded.") + var caAlg = flag.String("ca-alg", "rsa", "Root keypair algorithm: RSA or ECDSA. Only used if generating new.") var domains = flag.String("domains", "", "Comma separated domain names to include as Server Alternative Names.") var ipAddresses = flag.String("ip-addresses", "", "Comma separated IP addresses to include as Server Alternative Names.") flag.Usage = func() { @@ -317,6 +345,13 @@ will not overwrite existing keys or certificates. flag.Usage() os.Exit(1) } + alg := x509.RSA + if strings.ToLower(*caAlg) == "ecdsa" { + alg = x509.ECDSA + } else if strings.ToLower(*caCert) != "rsa" { + fmt.Printf("Unrecognized algorithm: %s (use RSA or ECDSA)\n", *caAlg) + os.Exit(1) + } if len(flag.Args()) > 0 { fmt.Printf("Extra arguments: %s (maybe there are spaces in your domain list?)\n", flag.Args()) os.Exit(1) @@ -336,7 +371,7 @@ will not overwrite existing keys or certificates. os.Exit(1) } } - issuer, err := getIssuer(*caKey, *caCert) + issuer, err := getIssuer(*caKey, *caCert, alg) if err != nil { return err }